【Shiro】Shiro 的学习教程(五)之 SpringBoot 集成 Shiro + JWT

发布于:2024-09-19 ⋅ 阅读:(136) ⋅ 点赞:(0)

与 Spring 集成:与 Spring 集成
与 SpringBoot 集成:与 SpringBoot 集成

1、SpringBoot + Shiro + Jwt

①:引入 pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
<!-- shiro -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring-boot-starter</artifactId>
    <version>1.5.3</version>
</dependency>
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.4.0</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.75</version>
</dependency>

②:配置信息 yml:

spring:
  redis:
    host: localhost
    port: 6379
    password:
    timeout: 2000s
    lettuce:
      pool:
        max-active: 8  
        max-wait: -1ms 
        max-idle: 8    
        min-idle: 0 

③:JWT 工具类 JwtUtil

@Slf4j
public class JwtUtil {

    private static final long EXPIRE_TIME = 30 * 60 * 1000;

    public static String sign(String username, String secret) {
        Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
        Algorithm algorithm = Algorithm.HMAC256(secret);
        // 附带username信息
        return JWT.create()
                .withClaim("username", username)
                .withExpiresAt(date)
                .sign(algorithm);

    }

    public static String getUsername(String token) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim("username").asString();
        } catch (JWTDecodeException e) {
            return null;
        }
    }

    public static boolean verify(String token, String username, String secret) {
        try {
            //根据密码生成JWT效验器
            Algorithm algorithm = Algorithm.HMAC256(secret);
            JWTVerifier verifier = JWT.require(algorithm)
                    .withClaim("username", username)
                    .build();
            //效验TOKEN
            DecodedJWT jwt = verifier.verify(token);
            log.info("登录验证成功!");
            return true;
        } catch (Exception exception) {
            log.error("JwtUtil登录验证失败!");
            return false;
        }
    }

}

④:重写 token

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;
    }

}

⑤:重写 filter

@Slf4j
public class JwtFilter extends BasicHttpAuthenticationFilter implements Filter {

    // 对跨域提供支持
    @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);
    }

    // 执行登录认证
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        try {
            return executeLogin(request, response);
        } catch (Exception e) {
            log.error("JwtFilter过滤验证失败!");
            return false;
        }
    }

    // 执行登录
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws IOException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String token = httpServletRequest.getHeader("SevenHee-Token");
        JwtToken jwtToken = new JwtToken(token);
        // 提交给realm进行登入,如果错误他会抛出异常并被捕获
        try {
            getSubject(request, response).login(jwtToken);
            // 如果没有抛出异常则代表登入成功,返回true
            return true;
        } catch (AuthenticationException e) {
            return false;
        }
    }

    // 认证失败时,自定义返回json数据
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("msg", "认证失败");
        Object parse = JSONObject.toJSON(jsonObject);
        response.setCharacterEncoding("utf-8");
        response.getWriter().print(parse);
        return super.onAccessDenied(request, response);
    }

}

⑥:shiro 配置类

@Configuration
public class ShiroConfig {

    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
        //创建拦截链实例
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        //设置拦截器链//
        //设置拦截链map
        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        //放行请求
        filterChainDefinitionMap.put("/shiro/getToken", "anon");

        //自定义过滤器//
        // 添加自己的自定义拦截器并且取名为jwt
        Map<String, Filter> filterMap = new HashMap<>(1);
        filterMap.put("jwt", new JwtFilter());
        shiroFilterFactoryBean.setFilters(filterMap);
        //拦截链配置,从上向下顺序执行,一般将jwt过滤器放在最为下边
        filterChainDefinitionMap.put("/**", "jwt");
        //配置拦截链到过滤器工厂
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        //登录url、未授权url//
        //设置组登录请求,其他路径一律自动跳转到这里
        shiroFilterFactoryBean.setLoginUrl("/login");
        //未授权跳转路径
        shiroFilterFactoryBean.setUnauthorizedUrl("/notRole");
        //返回实例
        return shiroFilterFactoryBean;
    }

    // 安全管理器
    @Bean
    public DefaultWebSecurityManager securityManager(Realm shiroRealm) {
        //创建默认的web安全管理器
        DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();
        //配置shiro的自定义认证逻辑
        defaultSecurityManager.setRealm(shiroRealm);
        /*
         * 关闭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);
        defaultSecurityManager.setSubjectDAO(subjectDAO);
        //返回安全管理器实例
        return defaultSecurityManager;
    }

    @Bean
    public Realm shiroRealm(){
        return new ShiroRealm();
    }

    // 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions)
    @Bean
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    // 开启aop注解支持
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

}

⑦:shiro 认证、授权类

@Slf4j
public class ShiroRealm extends AuthorizingRealm {

    @Autowired
    private RedisTemplate redisTemplate;

    // 设置对应的token类型
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //权限认证
        log.info("开始进行权限认证.............");
        //获取用户名
        String token = (String) SecurityUtils.getSubject().getPrincipal();
        String username = JwtUtil.getUsername(token);
        //模拟数据库校验,写死用户名 test,其他用户无法登陆成功
        if (!GlobalConstant.USER_NAME.equals(username)) {
            return null;
        }
        //创建授权信息
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //创建set集合,存储权限
        HashSet<String> rootSet = new HashSet<>();
        //添加权限
        rootSet.add("user:show");
        rootSet.add("user:admin");
        //设置权限
        info.setStringPermissions(rootSet);
        //返回权限实例
        return info;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        log.info("开始身份认证.....................");
        //获取token
        String token = (String) authenticationToken.getCredentials();
        //创建字符串,存储用户信息
        String username = null;
        try {
            //获取用户名
            username = JwtUtil.getUsername(token);
        } catch (AuthenticationException e) {
            throw new AuthenticationException("heard的token拼写错误或者值为空");
        }
        if (username == null) {
            throw new AuthenticationException("token无效");
        }
        // 校验token是否超时失效 & 或者账号密码是否错误
        if (!jwtTokenRefresh(token, username, GlobalConstant.PASSWORD)) {
            throw new AuthenticationException("Token失效,请重新登录!");
        }
        //返回身份认证信息
        return new SimpleAuthenticationInfo(token, token, this.getName());
    }

    // 刷新token
    public boolean jwtTokenRefresh(String token, String userName, String passWord) {
        String redisToken = (String) redisTemplate.opsForValue().get(token);
        if (redisToken != null) {
            if (!JwtUtil.verify(redisToken, userName, passWord)) {
                String newToken = JwtUtil.sign(userName, passWord);
                //设置redis缓存
                redisTemplate.opsForValue().set(token, newToken, GlobalConstant.REDIS_EXPIRE_TIME * 2 / 1000, TimeUnit.SECONDS);
            }
            return true;
        }
        return false;
    }

}

⑧:常量类

public interface GlobalConstant {

    // redis 过期时间
    Integer REDIS_EXPIRE_TIME = 1800000;

    // 测试用户
    String USER_NAME = "test";

    // 测试密码
    String PASSWORD = "123456";

}

⑨:测试接口

@RestController
@RequestMapping("/shiro")
public class ShiroController {

    @Autowired
    private RedisTemplate redisTemplate;

    @RequestMapping("/getToken")
    public String getToken() {
        String token = JwtUtil.sign(GlobalConstant.USER_NAME, GlobalConstant.PASSWORD);
        redisTemplate.opsForValue().set(token, token, GlobalConstant.REDIS_EXPIRE_TIME * 2 / 1000, TimeUnit.SECONDS);
        return token;
    }

    @RequiresPermissions("user:admin")
    @RequestMapping("/test")
    public String test() {
        System.out.println("进入测试,只有带有令牌才可以进入该方法");
        return "访问接口成功";
    }

}

⑩:postman 测试

在这里插入图片描述

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到