shiro+jwt登陆验证实现教程

 更新时间:2026年03月13日 10:08:34   作者:我是菜鸟呐  
本文介绍了如何使用Shiro和JWT实现登录认证,并通过自定义Realm和过滤器来完成身份验证和授权,通过测试,展示了如何携带JWT令牌进行接口访问,并验证了其有效期

学习目标

例如:

  • 一小时实现shiro+jwt实现登录认证

学习内容

例如:

  1. 了解shiro框架
  2. 使用jwt生成身份令牌

项目目录:

直接上代码

这个过滤器是我们的重点,这里我们继承的是Shiro内置的BasicHttpAuthenticationFilter ,一个可以内置了可以自动登录方法的的过滤器,有些同学继承AuthenticatingFilter也是可以的,根据个人情况。

@Component
@Slf4j
public class JWTFilter extends BasicHttpAuthenticationFilter {


    /**
     * 认证之前执行该方法
     * @param request
     * @param response
     * @param mappedValue
     * @return
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        Subject subject = SecurityUtils.getSubject();
        return  null != subject && subject.isAuthenticated();
    }

    /**
     * 认证未通过执行该方法
     * @param request
     * @param response
     * @return
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response){
        //完成token登入
        //1.检查请求头中是否含有token
        HttpServletRequest httpServletRequest= (HttpServletRequest) request;
        String token = httpServletRequest.getHeader("Authorization");
        //2. 如果客户端没有携带token,拦下请求
        if(null==token||"".equals(token)){
            responseTokenError(response, RCode.INVALID_TOKEN);
            return false;
        }
        //3. 如果有,对进行进行token验证
        JWTToken jwtToken = new JWTToken(token);
        try {
            SecurityUtils.getSubject().login(jwtToken);
        } catch (AuthenticationException e) {
            log.error(e.getMessage());
            responseTokenError(response,RCode.AUTH_ERROR);
            return false;
        }

        return true;
    }

    /**
     * 对跨域提供支持
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
        // 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }
    /**
     * 无需转发,直接返回Response信息 Token认证错误
     */
    private void responseTokenError(ServletResponse response,RCode rCode) {
        HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
        httpServletResponse.setStatus(HttpStatus.OK.value());
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("application/json; charset=utf-8");
        try (PrintWriter out = httpServletResponse.getWriter()) {
            String data = new Gson().toJson(R.error(rCode));
            out.append(data);
        } catch (IOException e) {
            e.printStackTrace();
            log.error(e.getMessage());
        }
    }
}

整合shiro的配置,shiro可以帮我们做很多事情,比如创建realm、配置shiro过滤器工厂等。

@Configuration
public class ShiroConfig {

    @Bean("securityManager")
    public DefaultWebSecurityManager getManager(UserRealm realm) {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        // 使用自己的realm
        manager.setRealm(realm);

        /*
         * 关闭shiro自带的session,详情见文档
         * http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29
         */
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        manager.setSubjectDAO(subjectDAO);

        return manager;
    }

    @Bean("shiroFilter")
    public ShiroFilterFactoryBean factory(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();

        // 添加自己的过滤器并且取名为jwt
        Map<String, Filter> filterMap = new HashMap<>();
        filterMap.put("jwt", new JWTFilter());
        factoryBean.setFilters(filterMap);

        factoryBean.setSecurityManager(securityManager);
        /*
         * 自定义url规则
         * http://shiro.apache.org/web.html#urls-
         */
        Map<String, String> filterRuleMap = new HashMap<>();
        // 所有请求通过我们自己的JWT Filter
        filterRuleMap.put("/**", "jwt");
        // 访问401和404页面不通过我们的Filter
        filterRuleMap.put("/admin/sys/user/login", "anon");
        filterRuleMap.put("/user/imgCode", "anon");
        //开放API文档接口
        filterRuleMap.put("/swagger-ui.html", "anon");
        filterRuleMap.put("/webjars/**","anon");
        filterRuleMap.put("/swagger-resources/**","anon");
        filterRuleMap.put("/v2/**","anon");
        //sql监控
        filterRuleMap.put("/druid/**","anon");
        factoryBean.setFilterChainDefinitionMap(filterRuleMap);
        return factoryBean;
    }
    /**
     * 下面的代码是添加注解支持
     */
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        // 强制使用cglib,防止重复代理和可能引起代理出错的问题
        // https://zhuanlan.zhihu.com/p/29161098
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
}

自定义realm,因为我没有连接数据库这里的用户名和密码都是写死的,根据业务需要可以连接数据库获取

@Service
public class UserRealm extends AuthorizingRealm {
    /**
     * 大坑!,必须重写此方法,不然Shiro会报错
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JWTToken;
    }
    /**
     * 只有当需要检测用户权限的时候才会调用此方法,例如checkRole,checkPermission之类的
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }
    /**
     * 默认使用此方法进行用户名正确与否验证,错误抛出异常即可。
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
        String token = (String) auth.getCredentials();
        if(JWTUtils.isExpire(token)){
            throw new AuthenticationException("token过期,请重新登入!");
        }
        // 解密获得username,用于和数据库进行对比
        String username = JWTUtils.getUsername(token);

        if (username == null) {
            throw new AuthenticationException("token错误,请重新登入!");
        }

        String name="admin";
        String pwd="3bcbb857c763d1429a24959cb8de2593";
        if (! JWTUtils.verify(token, name, pwd)) {

            throw new AuthenticationException("密码错误!");
        }

        return new SimpleAuthenticationInfo("activeUser", token, getName());
    }
}
public class JWTToken implements AuthenticationToken {

    // 密钥
    private String token;

    public JWTToken(String token) {
        this.token = token;
    }

    @Override
    public Object getPrincipal() {
        return token;
    }

    @Override
    public Object getCredentials() {
        return token;
    }
}

jwt生成token

public class JWTUtils {
    /**
     * 过期时间6小时
     */
    private static final long EXPIRE_TIME = 6 * 60 * 60 * 1000;

    /**
     * 校验token是否正确
     *
     * @param token  密钥
     * @param secret 用户的密码
     * @return 是否正确
     */
    public static boolean verify(String token, String username, String secret) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(secret);
            JWTVerifier verifier = JWT.require(algorithm)
                    .withClaim("username", username)
                    .build();
            DecodedJWT jwt = verifier.verify(token);
            return true;
        } catch (Exception exception) {
            return false;
        }
    }

    /**
     * 获得token中的信息无需secret解密也能获得
     *
     * @return token中包含的用户名
     */
    public static String getUsername(String token) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim("username").asString();
        } catch (JWTDecodeException e) {
            return null;
        }
    }

    /**
     * 生成签名,2min后过期
     *
     * @param username 用户名
     * @param secret   用户的密码
     * @return 加密的token
     */
    public static String sign(String username, String secret) {
        try {
            Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
            Algorithm algorithm = Algorithm.HMAC256(secret);
            // 附带username信息
            return JWT.create()
                    .withClaim("username", username)
                    .withExpiresAt(date)
                    .sign(algorithm);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 判断过期
     *
     * @param token
     * @return
     */
    public static boolean isExpire(String token) {
        DecodedJWT jwt = JWT.decode(token);
        return System.currentTimeMillis() > jwt.getExpiresAt().getTime();
    }
}

使用md5进行加密

    /**
     * 密码加密
     * @return
     */
    public static String md5Encryption(String source,String salt){
        String algorithmName = "MD5";//加密算法
        int hashIterations = 1024;//加密次数
        SimpleHash simpleHash = new SimpleHash(algorithmName,source,salt,hashIterations);
        return simpleHash+"";
    }

重载类:用于结果返回

@Data
@AllArgsConstructor
@NoArgsConstructor
public class R {
    private Integer code ;
    private String msg;
    private Object data;



    public static R ok(Object data){
        return new R(RCode.SUCCESS.getCode(),
                RCode.SUCCESS.getMsg(),data);
    }
    public static R ok(){
        return R.ok(null);
    }
    public static R error(RCode rCode){
        return new  R(rCode.getCode(),rCode.getMsg(),null);
    }
    public static R error(String message){
        return new  R(RCode.SYS_ERROR.getCode(),message,null);
    }
    public static R error(){
        return R.error(RCode.ERROR);
    }
}

枚举类

@AllArgsConstructor
@Getter
public enum RCode {
    SUCCESS(0,"操作成功"),
    ERROR(1,"操作失败"),
    SYS_ERROR(500,"系统错误,请与管理员联系"),
    SYS_VALID_ERROR(501,"参数校验错误"),
    USER_EXIST(1001,"用户已存在"),
    USER_NOT_EXIST(1002,"用户不存在!"),
    VALID_ERROR(40001,"验证错误!"),
    INVALID_TOKEN(40410,"Token无效,您无权访问该接口"),
    AUTH_ERROR(40411,"用户名或密码不存在!"),
    NO_AUTH_ERROR(40412,"无权访问!"),
    QUERY_EXIST(1002,"此数据以重复")
    ;
    private Integer code;
    private String msg;

}

到这里就可以开始写controller了,写了一个登陆的接口和两个测试的接口

@RestController
@CrossOrigin
@RequestMapping("/admin/sys/user")
public class TestController {

    @GetMapping("/login")
    public R login(String username,String password){
        String MD5Password = MD5Utils.md5Encryption(username, password);
        System.out.println(MD5Password);
        String token= JWTUtils.sign(username, MD5Password);
        System.out.println(token);
        JWTToken jwtToken = new JWTToken(token);
        try {
            SecurityUtils.getSubject().login(jwtToken);
        } catch (AuthenticationException e) {
            return R.error("token 错误!");
        }
        Map<String,Object> maps = new HashMap<>();
        maps.put("token",token);
        return R.ok(maps);
    }

    @GetMapping("all")
    public String all(){
        return "all";
    }
	@GetMapping("list")
    public R list(){
        return R.ok("list");
    }
}

测试结果

如果我不携带token是不能访问任何接口的,除登陆接口外

下面访问登陆接口,是可以访问的,并且返回的token令牌,我们可以拿着这个token去访问其他接口

带着token就可以正常访问了,有效期是6个小时

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • win10操作系统下重启电脑java环境变量失效

    win10操作系统下重启电脑java环境变量失效

    这篇文章主要介绍了win10操作系统下重启电脑java环境变量失效,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09
  • IntelliJ IDEA 中配置 Spring MVC 环境的详细步骤及问题解决

    IntelliJ IDEA 中配置 Spring MVC 环境的详细步

    这篇文章主要介绍了IntelliJ IDEA 中配置 Spring MVC 环境的详细步骤及问题解决,本文分步骤结合实例给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2025-04-04
  • SpringMVC中的@RequestMapping注解解析

    SpringMVC中的@RequestMapping注解解析

    这篇文章主要介绍了SpringMVC中的@RequestMapping注解解析,SpringMVC使用@RequestMapping注解为控制器指定可以处理哪些 URL 请求,在控制器的类定义及方法定义处都可标注@RequestMapping,需要的朋友可以参考下
    2023-12-12
  • SpringMVC源码解读之HandlerMapping - AbstractUrlHandlerMapping系列request分发

    SpringMVC源码解读之HandlerMapping - AbstractUrlHandlerMapping系列re

    这篇文章主要介绍了SpringMVC源码解读之HandlerMapping - AbstractUrlHandlerMapping系列request分发 的相关资料,需要的朋友可以参考下
    2016-02-02
  • Java字符拼接成字符串的注意点详解

    Java字符拼接成字符串的注意点详解

    这篇文章主要介绍了Java字符拼接成字符串的注意点详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-07-07
  • Java微信退款开发

    Java微信退款开发

    这篇文章主要为大家详细介绍了Java微信退款开发的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-09-09
  • Spring-Boot框架初步搭建

    Spring-Boot框架初步搭建

    本篇文章主要介绍了Spring-Boot框架初步搭建,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-03-03
  • Spring多线程通过@Scheduled实现定时任务

    Spring多线程通过@Scheduled实现定时任务

    这篇文章主要介绍了Spring多线程通过@Scheduled实现定时任务,@Scheduled 定时任务调度注解,是spring定时任务中最重要的,下文关于其具体介绍,需要的小伙伴可以参考一下
    2022-05-05
  • Java中的ThreadLocal与ThreadLocalMap详解

    Java中的ThreadLocal与ThreadLocalMap详解

    这篇文章主要介绍了Java中的ThreadLocal与ThreadLocalMap详解,ThreadLocal 是一个线程局部变量,其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,需要的朋友可以参考下
    2023-09-09
  • Gradle的使用教程详解

    Gradle的使用教程详解

    Gradle它使用一种基于Groovy的特定领域语言(DSL)来声明项目设置,目前也增加了基于Kotlin语言的kotlin-based DSL,抛弃了基于XML的各种繁琐配置,下面通过本文给大家介绍Gradle的使用教程,感兴趣的朋友一起看看吧
    2020-09-09

最新评论