Sakurairo

加载中...

首页文章专题归档关于友链

主题

返回文章列表
java

Shiro入门-配置自定义jwtFilter和自定义realm

#Java #shiro #鉴权 #J2EE #Spring Spring Boot集成Shiro+JWT实现权限控制 项目依赖配置 在pom.xml中添加必要的依赖: 核心组件说明 2.1 JWTToken 实现Authenticatio...

2024-11-073 分钟 53 阅读

发布于 2024-11-07 02:44

·

作者:八云澈

#Java #shiro #鉴权 #J2EE #Spring

Spring Boot集成Shiro+JWT实现权限控制

1. 项目依赖配置

pom.xml中添加必要的依赖:

xml
1<dependencies>2    <!-- Shiro Spring -->3    <dependency>4        <groupId>org.apache.shiro</groupId>5        <artifactId>shiro-spring</artifactId>6        <version>1.9.0</version>7    </dependency>8    9    <!-- JWT -->10    <dependency>11        <groupId>com.auth0</groupId>12        <artifactId>java-jwt</artifactId>13        <version>4.3.0</version>14    </dependency>15</dependencies>

2. 核心组件说明

2.1 JWTToken

实现AuthenticationToken接口,用于封装JWT令牌:

  • 用于替代Shiro默认的UsernamePasswordToken
  • 实现getPrincipal()和getCredentials()方法
  • 用于JWT令牌的身份验证

示例:

Java
1public class JwtToken implements AuthenticationToken {  2    private String token;  3  4    public JwtToken(String token) {  5        this.token = token;  6    }  7  8    @Override  9    public Object getPrincipal() {  10        return token;  11    }  12  13    @Override  14    public Object getCredentials() {  15        return token;  16    }  17}

2.2 JWTUtil

JWT工具类,提供以下功能:

  • createToken(): 创建JWT令牌
  • getUsername(): 从令牌中获取用户名
  • verify(): 验证令牌有效性
  • 支持令牌过期时间设置

示例:

Java
1@Component  2public class JwtUtil {  3    @Value("${jwt.secret:hasuneMiku}")  4    private String secret;  5  6    @Value("${jwt.expiration:86400}")  7    private Long expiration;  8  9    public String generateToken(String username) {  10        return Jwts.builder().setSubject(username).setIssuedAt(new Date()).setExpiration(new Date(System.currentTimeMillis() + expiration * 1000)).signWith(getSigningKey(), SignatureAlgorithm.HS256).compact();  11    }  12  13    public String generateToken(String userId, String userName) {  14        return Jwts.builder().setSubject(userId).setSubject(userName).setIssuedAt(new Date()).setExpiration(new Date(System.currentTimeMillis() + expiration * 1000)).signWith(getSigningKey(), SignatureAlgorithm.HS256).compact();  15    }  16  17    public String getUserNameFromToken(String token) {  18        return Jwts.parserBuilder().setSigningKey(getSigningKey()).build().parseClaimsJws(token).getBody().getSubject();  19    }  20  21    public boolean validateToken(String token) throws JwtException {  22        try {  23            Jwts.parserBuilder().setSigningKey(getSigningKey()).build().parseClaimsJws(token);  24            return true;  25        } catch (JwtException e) {  26            return false;  27        }  28    }  29  30    private SecretKey getSigningKey() {  31        byte[] keyBytes = Decoders.BASE64.decode(secret.replace("\r\n", ""));  32        return Keys.hmacShaKeyFor(keyBytes);  33    }  34  35  36}

2.3 JWTFilter

继承BasicHttpAuthenticationFilter,实现JWT过滤:

  • isLoginAttempt(): 判断是否是登录请求
  • executeLogin(): 执行登录逻辑
  • isAccessAllowed(): 判断是否允许访问
  • 从请求头中获取Authorization信息

示例:

Java
1public class JWTFilter extends BasicHttpAuthenticationFilter {  2  3    @Override  4    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {  5        HttpServletRequest req = (HttpServletRequest) request;  6        String authorization = req.getHeader("Authorization");  7        return authorization != null;  8    }  9  10    @Override  11    protected boolean executeLogin(ServletRequest request, ServletResponse response) {  12        HttpServletRequest req = (HttpServletRequest) request;  13        String token = req.getHeader("Authorization");  14        JwtToken jwtToken = new JwtToken(token);  15        getSubject(request, response).login(jwtToken);  16        return true;  17    }  18  19    @Override  20    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {  21        if (isLoginAttempt(request, response)) {  22            try {  23                executeLogin(request, response);  24                return true;  25            } catch (Exception e) {  26                return false;  27            }  28        }  29        return false;  30    }  31}

2.4 CustomRealm

自定义Realm实现,继承AuthorizingRealm

  • doGetAuthorizationInfo(): 处理授权逻辑
  • doGetAuthenticationInfo(): 处理认证逻辑
  • supports(): 支持JWT令牌验证

示例:

Java
1public class JwtRealm extends AuthorizingRealm {  2    @Autowired  3    private UserService userService;  4  5    @Autowired  6    private PermService permService;  7  8    @Autowired  9    private RoleService roleService;  10  11    @Autowired  12    private JwtUtil jwtUtil;  13  14    @Override  15    public boolean supports(AuthenticationToken token) {  16        return token instanceof JwtToken;  17    }  18  19    @Override  20    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {  21        String username = principals.toString();  22        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();  23        // 获取用户权限  24        Set<String> permissions = permService.getPermByuserName(username);  25        Set<String> roles = roleService.getRolesByUserName(username);  26        info.setStringPermissions(permissions);  27        info.setRoles(roles);  28        return info;  29    }  30  31    @Override  32    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)  33  34            throws AuthenticationException {  35        String token = (String) authenticationToken.getCredentials();  36        String username = jwtUtil.getUserNameFromToken(token);  37  38        if (username == null || !jwtUtil.validateToken(token)) {  39            throw new AuthenticationException("token invalid");  40        }  41  42        // 验证用户是否存在  43        if (!userService.exist(username)) {  44            throw new UnknownAccountException("User doesn't exist!");  45        }  46  47        return new SimpleAuthenticationInfo(username, token, getName());  48    }  49  50}

3. 配置类说明

ShiroConfig配置类包含:

  1. ShiroFilterFactoryBean配置:

    • 配置JWT过滤器
    • 设置过滤规则
    • 配置无权限跳转
  2. SecurityManager配置:

    • 设置自定义Realm
    • 关闭Session管理
    • 配置为无状态模式
  3. 开启注解支持:

    • 配置DefaultAdvisorAutoProxyCreator
    • 配置AuthorizationAttributeSourceAdvisor
    • 配置LifecycleBeanPostProcessor

示例:

Java
1 2@Configuration  3public class ShiroConfig {  4  5    @Bean  6    public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {  7        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();  8        factoryBean.setSecurityManager(securityManager);  9  10        // 添加jwt过滤器  11        Map<String, Filter> filterMap = new HashMap<>();  12        filterMap.put("jwt", new JWTFilter());  13        factoryBean.setFilters(filterMap);  14  15        // 设置无权限时跳转的url  16        factoryBean.setUnauthorizedUrl("/unauthorized");  17  18        // 设置过滤规则  19        Map<String, String> filterRuleMap = new HashMap<>();  20  21        // 登录接口放行  22        filterRuleMap.put("/api/auth/**", "anon");  23  24        // 其他所有路径都需要jwt验证  25        filterRuleMap.put("/**", "jwt");  26        factoryBean.setFilterChainDefinitionMap(filterRuleMap);  27  28        return factoryBean;  29    }  30  31    @Bean  32    public DefaultWebSecurityManager securityManager(JwtRealm realm) {  33        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();  34        securityManager.setRealm(realm);  35  36        // 关闭shiro自带的session  37        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();  38        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();  39        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);  40        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);  41        securityManager.setSubjectDAO(subjectDAO);  42  43        return securityManager;  44    }  45  46    @Bean  47    public JwtRealm realm() {  48        return new JwtRealm();  49    }  50  51    /**  52     * 开启Shiro的注解支持  53     */  54    @Bean  55    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {  56        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();  57        advisorAutoProxyCreator.setProxyTargetClass(true);  58        return advisorAutoProxyCreator;  59    }  60  61    @Bean  62    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {  63        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();  64        advisor.setSecurityManager(securityManager);  65        return advisor;  66    }  67  68    @Bean  69    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {  70        return new LifecycleBeanPostProcessor();  71    }  72}

4. 权限控制使用方式

4.1 注解方式

Java
1// 角色控制2@RequiresRoles("admin")3public void adminOperation() {}4 5// 权限控制6@RequiresPermissions("user:read")7public void readOperation() {}8 9// 登录认证10@RequiresAuthentication11public void authenticatedOperation() {}

4.2 编程方式

Java
1Subject subject = SecurityUtils.getSubject();2// 检查角色3subject.checkRole("admin");4// 检查权限5subject.checkPermission("user:read");

5. 接口调用流程

  1. 用户登录:

    • 调用登录接口
    • 验证用户名密码
    • 生成JWT令牌返回
  2. 请求接口:

    • 请求头携带令牌:Authorization: {token}
    • JWTFilter进行令牌解析
    • CustomRealm进行权限验证
    • 返回请求结果

6. UserService接口定义

需要实现以下方法:

Java
1public interface UserService {2    // 获取用户权限列表3    Set<String> getPermissions(String username);4    5    // 获取用户角色列表6    Set<String> getRoles(String username);7    8    // 检查用户是否存在9    boolean exists(String username);10    11    // 验证用户密码12    boolean checkPassword(String username, String password);13}

7. 常见问题与解决方案

7.1 登录失败问题

  1. 问题: 明明用户名密码正确,但始终登录失败 解决方案:
    • 检查密码加密方式是否一致
    • 确认salt值的使用是否正确
    Java
    1// 正确的密码校验方式2String encryptPassword = new SimpleHash(3    "MD5",                          // 算法名称4    password,                       // 原密码5    ByteSource.Util.bytes(salt),    // 盐值6    1024                           // 迭代次数7).toHex();

7.2 授权失败问题

  1. 问题: 注解不生效 解决方案:
    • 确认已经配置了AOP支持
    • 检查是否启用了注解支持
    Java
    1@EnableAspectJAutoProxy2@Configuration3public class ShiroConfig {4    // ...配置代码5}

7.3 Session问题

  1. 问题: Session频繁失效 解决方案:
    • 调整session超时时间
    • 确认是否存在集群环境下的session同步问题
    Java
    1sessionManager.setGlobalSessionTimeout(3600000); // 设置为1小时

7.4 跨域问题

  1. 问题: 跨域请求被拦截 解决方案:
    Java
    1@Bean2public FilterRegistrationBean corsFilter() {3    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();4    CorsConfiguration config = new CorsConfiguration();5    config.setAllowCredentials(true);6    config.addAllowedOrigin("*");7    config.addAllowedHeader("*");8    config.addAllowedMethod("*");9    source.registerCorsConfiguration("/**", config);10    FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));11    bean.setOrder(0);12    return bean;13}

7.5 性能优化建议

  1. 缓存配置:

    Java
    1@Bean2public CacheManager cacheManager() {3    EhCacheManager cacheManager = new EhCacheManager();4    cacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");5    return cacheManager;6}
  2. 并发性能优化:

    Java
    1@Bean2public SecurityManager securityManager() {3    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();4    // 开启并发验证5    ((DefaultSubjectDAO)securityManager.getSubjectDAO())6        .setSessionStorageEvaluator(new DefaultSessionStorageEvaluator());7    return securityManager;8}

最佳实践建议

  1. 始终使用安全的密码加密方式
  2. 合理配置缓存,提高性能
  3. 正确处理异常,提供友好的错误提示
  4. 定期清理过期session
  5. 使用注解进行权限控制时要考虑粒度
  6. 在生产环境中要禁用开发模式配置
八云澈

Writer · Recorder

八云澈(Bayunche)

开发者 / 写作者 / 普通生活记录者 · 中国 · 深圳

喜欢把工作里的技术问题、读书时的触动、生活中的琐碎观察慢慢写下来。既想把复杂问题讲清楚,也想留住那些很快会被忘掉的日常。

Java 后端Go 工程化读书札记生活记录日常随笔

Continue Reading

相关文章

更多文章

上一篇

Spring原理

2024-11-06

已经是最后一篇了。

评论区

读完这篇文章后,如果你也有类似经验或不同看法,欢迎继续交流。

💬 评论