在前后端分离项目中使用SpringBoot集成Shiro

前言

       这次在处理一个小项目时用到了前后端分离,服务端使用springboot2.x。权限验证使用了Shiro。前后端分离首先需要解决的是跨域问题,POST接口跨域时会预发送一个OPTIONS请求,浏览器收到响应后会继续执行POST请求。 前后端分离后为了保持会话状态使用session持久化插件shiro-redis,持久化session可以持久化到关系型数据库,也可以持久化到非关系型数据库(主要是重写SessionDao)。Shiro已提供了SessionDao接口和抽象类。如果项目中用到Swagger的话,还需要把swagger相关url放行。

    

搭建依赖

1
2
3
4
5
6
7
8
9
10
11
12
<dependency>
    <!--session持久化插件-->
    <groupId>org.crazycake</groupId>
    <artifactId>shiro-redis</artifactId>
    <version>3.2.3</version>
</dependency>
<dependency>
    <!--spring shiro依赖-->
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.4.1</version>
</dependency>

 Shiro权限配置

1、ShiroConfig。这里主要是shiro核心配置。比如SecurityManager、SessionManager、CacheManager。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
public class ShiroConfig {
 
    @Value("${spring.redis.shiro.host}")
    private String host;
    @Value("${spring.redis.shiro.port}")
    private int port;
    @Value("${spring.redis.shiro.timeout}")
    private int timeout;
    @Value("${spring.redis.shiro.password}")
    private String password;
 
 
    /**
     * 权限规则配置
     **/
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
 
        Map<String, Filter> filters = shiroFilterFactoryBean.getFilters();
        filters.put("authc", new MyFormAuthorizationFilter());
 
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
 
        //swagger资源不拦截
        filterChainDefinitionMap.put("/swagger-ui.html", "anon");
        filterChainDefinitionMap.put("/swagger-resources/**/**", "anon");
        filterChainDefinitionMap.put("/v2/api-docs", "anon");
        filterChainDefinitionMap.put("/webjars/springfox-swagger-ui/**", "anon");
        filterChainDefinitionMap.put("/configuration/security", "anon");
        filterChainDefinitionMap.put("/configuration/ui", "anon");
 
        filterChainDefinitionMap.put("/login/ajaxLogin", "anon");
        filterChainDefinitionMap.put("/login/unauth", "anon");
        filterChainDefinitionMap.put("/login/logout", "anon");
        filterChainDefinitionMap.put("/login/register","anon");
        filterChainDefinitionMap.put("/**", "authc");
 
        shiroFilterFactoryBean.setLoginUrl("/login/unauth");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }
 
 
    /**
     * shiro安全管理器(权限验证核心配置)
     **/
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myShiroRealm());
        securityManager.setSessionManager(sessionManager());
        securityManager.setCacheManager(cacheManager());
 
        return securityManager;
    }
 
    /**
     * 会话管理
     **/
    @Bean
    public SessionManager sessionManager() {
        MySessionManager sessionManager = new MySessionManager();
        sessionManager.setSessionIdUrlRewritingEnabled(false); //取消登陆跳转URL后面的jsessionid参数
        sessionManager.setSessionDAO(sessionDAO());
        sessionManager.setGlobalSessionTimeout(-1);//不过期
        return sessionManager;
    }
 
    /**
     * 使用的是shiro-redis开源插件 缓存依赖
     **/
    @Bean
    public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(host+":"+port);
        redisManager.setTimeout(timeout);
        redisManager.setPassword(password);
        return redisManager;
    }
 
    /**
     * 使用的是shiro-redis开源插件 session持久化
     **/
    public RedisSessionDAO sessionDAO() {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        return redisSessionDAO;
    }
 
 
    /**
     * 缓存管理
     **/
    @Bean
    public CacheManager cacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        return redisCacheManager;
    }
 
 
    /**
     * 权限管理
     **/
    @Bean
    public MyShiroRealm myShiroRealm() {
 
        return new MyShiroRealm();
    }
}

 2、MyShiroRealm 用户身份验证、自定义权限。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class MyShiroRealm extends AuthorizingRealm {
 
    private Logger logger= LoggerFactory.getLogger(MyShiroRealm.class);
 
    @Resource
    UserDao userDao;
 
 
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        logger.info("===================权限验证==================");
        return null;
    }
 
 
 
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
 
        UsernamePasswordToken token=(UsernamePasswordToken) authenticationToken;
        User currentUser=userDao.findUser(token.getUsername());
        if(null == currentUser){
            throw new AuthenticationException("账户不存在");
        }
 
        if(!currentUser.getPassword().equals(new String(token.getPassword()))){
            throw new IncorrectCredentialsException("账户密码不正确");
        }
 
        if(currentUser.getIsdel()==1){
            throw new LockedAccountException("账户已冻结");
        }
 
        Subject subject = SecurityUtils.getSubject();
 
        BIUser biUser=new BIUser();
        biUser.setUserId(currentUser.getUserId());
        biUser.setOrgId(currentUser.getOrgid());
        biUser.setUserName(currentUser.getUsername());
        biUser.setPassword(currentUser.getPassword());
        biUser.setSessionId(subject.getSession().getId().toString());
        biUser.setIsdel(currentUser.getIsdel());
        biUser.setCreateTime(currentUser.getCreatetime());
 
        logger.info("======已授权"+biUser.toString()+"====");
 
        return new SimpleAuthenticationInfo(biUser,biUser.getPassword(),biUser.getUserName());
    }
}

 3、MySessionManager。shiro权限验证是根据客户端Cookie中的JSESSIONID值来确定身份是否合格。前后端分离后这个地方需要处理。客户端调用服务端登陆接口,验证通过后返回给客户端一个token值(这里我放的是sessionid)。客户端保存token值,然后调用其他接口时把token值放在header中。对前端来说也就是放在ajax的headers参数中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class MySessionManager extends DefaultWebSessionManager {
 
    private static final String AUTHORIZATION = "Authorization";
 
    private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
 
    public MySessionManager() {
    }
 
    @Override
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
        //从前端ajax headers中获取这个参数用来判断授权
        String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
        if (StringUtils.hasLength(id)) {
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
            return id;
        } else {
            //从前端的cookie中取值
            return super.getSessionId(request, response);
        }
 
    }
}

 4、MyFormAuthorizationFilter。对于跨域的POST请求,浏览器发起POST请求前都会发送一个OPTIONS请求已确定服务器是否可用,OPTIONS请求通过后继续执行POST请求,而shiro自带的权限验证是无法处理OPTIONS请求的,所以这里需要重写isAccessAllowed方法。

1
2
3
4
5
6
7
8
9
10
public class MyFormAuthorizationFilter extends FormAuthenticationFilter {
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) {
        HttpServletRequest httpServletRequest = WebUtils.toHttp(servletRequest);
        if ("OPTIONS".equals(httpServletRequest.getMethod())) {
            return true;
        }
        return super.isAccessAllowed(servletRequest, servletResponse, o);
    }
 
}

5、处理跨域

1
2
3
4
5
6
7
8
9
10
11
@Override
   public void addCorsMappings(CorsRegistry registry) {
       registry.addMapping("/**")
               .allowedOrigins("*")
               .allowedMethods("PUT", "DELETE", "GET", "POST")
               .allowedHeaders("*")
               .exposedHeaders("access-control-allow-headers", "access-control-allow-methods", "access-control-allow" +
                       "-origin", "access-control-max-age", "X-Frame-Options","Authorization")
               .allowCredentials(false).maxAge(3600);
 
   }

 

    

作者:sword-successful

出处:https://www.cnblogs.com/sword-successful/p/11093803.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   歪头儿在北京  阅读(6648)  评论(2编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
more_horiz
keyboard_arrow_up light_mode palette
选择主题
点击右上角即可分享
微信分享提示