千家信息网

如何使用Spring Boot+Shiro实现权限管理

发表于:2025-01-18 作者:千家信息网编辑
千家信息网最后更新 2025年01月18日,这篇文章主要介绍如何使用Spring Boot+Shiro实现权限管理,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!一:配置pom.xml文件
千家信息网最后更新 2025年01月18日如何使用Spring Boot+Shiro实现权限管理

这篇文章主要介绍如何使用Spring Boot+Shiro实现权限管理,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!

一:配置pom.xml文件

                                    org.apache.shiro                        shiro-spring                        1.4.0                

二:ShiroConfig配置类

@Configurationpublic class ShiroConfig {        @Bean("sessionManager")        public SessionManager sessionManager() {                DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();                sessionManager.setGlobalSessionTimeout(24 * 60 * 60 * 1000);                // 开启会话验证器                sessionManager.setSessionValidationSchedulerEnabled(true);                // 删除失效的session                sessionManager.setDeleteInvalidSessions(true);                // 指定sessionId,使用默认的"JSESSIONID"                sessionManager.setSessionIdCookieEnabled(true);                return sessionManager;        }        /**           * 我们在使用shiro的时候,首先都会先初始化SecurityManager,          * 然后往SecurityManager中注入shiro的其他组件,像sessionManager、realm等。         */        @Bean("securityManager")        public SecurityManager securityManager(OAuth3Realm oAuth3Realm, SessionManager sessionManager) {                DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();                securityManager.setRealm(oAuth3Realm);                securityManager.setSessionManager(sessionManager);                return securityManager;        }        @Bean("shiroFilter")        public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {                ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();                shiroFilter.setSecurityManager(securityManager);                // oauth过滤                Map filters = new HashMap<>();                filters.put("oauth3", new OAuth3Filter());                shiroFilter.setFilters(filters);                // 配置可以匿名访问的地址,可以根据实际情况自己添加,放行一些静态资源等,anon 表示放行                Map filterMap = new LinkedHashMap<>();                filterMap.put("/webjars/**", "anon");                filterMap.put("/druid/**", "anon");                filterMap.put("/app/**", "anon");                filterMap.put("/file/**", "anon");                filterMap.put("/shiro/login", "anon");                filterMap.put("/swagger/**", "anon");                filterMap.put("/v2/api-docs", "anon");                filterMap.put("/swagger-ui.html", "anon");                filterMap.put("/swagger-resources/**", "anon");                filterMap.put("/captcha.jpg", "anon");                // 其它通过自定义的OAuth3Filter进行过滤                filterMap.put("/**", "oauth3");                shiroFilter.setFilterChainDefinitionMap(filterMap);                return shiroFilter;        }        @Bean("lifecycleBeanPostProcessor")        public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {                return new LifecycleBeanPostProcessor();        }        /**          * @Title: defaultAdvisorAutoProxyCreator          * @Description: 扫描上下文,寻找所有的Advistor(通知器),将这些Advisor应用到所有符合切入点的Bean中          * @return   Spring的一个bean,由Advisor决定对哪些类的方法进行AOP代理。          * @return DefaultAdvisorAutoProxyCreator    返回类型          * @throws          *///      @Bean//      public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {//              DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();//              proxyCreator.setProxyTargetClass(true);//              return proxyCreator;//      }        /**          * @Title: authorizationAttributeSourceAdvisor          * @Description: 开启shiro aop注解支持          * @param securityManager          * @return   参数说明          * @return AuthorizationAttributeSourceAdvisor    返回类型          * @throws          */        @Bean        public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {                AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();                advisor.setSecurityManager(securityManager);                return advisor;        }}

其中,如果启用了DefaultAdvisorAutoProxyCreator的话,会导致二次代理的问题,Realm中的doGetAuthorizationInfo会重复调用2次。

三:自定义Shiro Filter类OAuth3Filter

public class OAuth3Filter extends AuthenticatingFilter {    @Override    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {        //获取请求token        String token = getRequestToken((HttpServletRequest) request);        if(StringUtils.isNullOrEmpty(token)){            return null;        }        return new OAuth3Token(token);    }    @Override    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {        //现在vue项目中使用axios发送http请求,每次请求都会多一次Request Method: OPTIONS请求,称为"预检"请求            if(((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name())){            return true;        }        return false;    }    @Override    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {        //获取请求token,如果token不存在,直接返回401        String token = getRequestToken((HttpServletRequest) request);        if(StringUtils.isNullOrEmpty(token)){            HttpServletResponse httpResponse = (HttpServletResponse) response;            httpResponse.setHeader("Access-Control-Allow-Credentials", "true");            httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin());            String json = new Gson().toJson(R.error(HttpStatus.SC_UNAUTHORIZED, "invalid token"));            httpResponse.getWriter().print(json);            return false;        }        return executeLogin(request, response);    }    @Override    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {        HttpServletResponse httpResponse = (HttpServletResponse) response;        httpResponse.setContentType("application/json;charset=utf-8");        httpResponse.setHeader("Access-Control-Allow-Credentials", "true");        httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin());        try {            //处理登录失败的异常            Throwable throwable = e.getCause() == null ? e : e.getCause();            R r = R.error(HttpStatus.SC_UNAUTHORIZED, throwable.getMessage());            String json = new Gson().toJson(r);            httpResponse.getWriter().print(json);        } catch (IOException e1) {        }        return false;    }    /**     * 获取请求的token     */    private String getRequestToken(HttpServletRequest httpRequest){        //从header中获取token        String token = httpRequest.getHeader("token");        //如果header中不存在token,则从参数中获取token        if(StringUtils.isNullOrEmpty(token)){            token = httpRequest.getParameter("token");        }        return "112233445566";//token;    }}

对于复杂的跨域请求,Vue会首先发送一个OPTIONS请求,进行验证。需要后端对所有接口统一处理放行OPTIONS方法(即返回200)即可

四:自定义Token

public class OAuth3Token implements AuthenticationToken {        private String token;        public OAuth3Token(String token) {                this.token = token;        }        /*         * 登录提交的用户名         *          */        @Override        public String getPrincipal() {                return token;        }        /*         * 只被Subject 知道的秘密值,比如我们登录提供的密码         *          */        @Override        public Object getCredentials() {                return token;        }}

五:自定义OAuth3Realm

@Componentpublic class OAuth3Realm extends AuthorizingRealm {        @Autowired        private ShiroService shiroService;        /*         * 判断此Realm是否支持此Token         */        @Override        public boolean supports(AuthenticationToken token) {                return token instanceof OAuth3Token;        }        /*         * 提供用户信息返回权限信息          *          */        @Override        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {                                System.out.println("=============获取已登录用户,权限信息============");                                SysUserEntity user = (SysUserEntity) principals.getPrimaryPrincipal();                Long userId = user.getUserId();                // 用户权限列表                Set permsSet = shiroService.getUserPermissions(userId);                SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();                info.setStringPermissions(permsSet);                return info;        }        /*         * 根据token获取认证信息         */        @Override        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {                System.out.println("=============获取已登录用户,用户信息============");                String accessToken = (String) token.getPrincipal();                // 根据accessToken,查询用户信息                SysUserTokenEntity tokenEntity = shiroService.queryByToken(accessToken);                // token失效                if (tokenEntity == null) {                        throw new IncorrectCredentialsException("token失效,请重新登录");                }                // 查询用户信息                SysUserEntity user = shiroService.queryUser(tokenEntity.getUserId());                // 账号锁定                if (user.getStatus().equals("00")) {                        throw new LockedAccountException("账号已被锁定,请联系管理员");                }                SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, accessToken, getName() // 返回一个唯一的Realm名字                );                return info;        }}

shiroService用来从数据库或者缓存中,查询用户信息和权限信息。

六:TokenGenerator

public class TokenGenerator {    public static String generateValue() {        return generateValue(UUID.randomUUID().toString());    }    private static final char[] hexCode = "0123456789abcdef".toCharArray();    public static String toHexString(byte[] data) {        if(data == null) {            return null;        }        StringBuilder r = new StringBuilder(data.length*2);        for ( byte b : data) {            r.append(hexCode[(b >> 4) & 0xF]);            r.append(hexCode[(b & 0xF)]);        }        return r.toString();    }    public static String generateValue(String param) {        try {            MessageDigest algorithm = MessageDigest.getInstance("MD5");            algorithm.reset();            algorithm.update(param.getBytes());            byte[] messageDigest = algorithm.digest();            return toHexString(messageDigest);        } catch (Exception e) {           e.printStackTrace();        }        return "";    }}

七:Controller层Demo

@RestController@RequestMapping("shiro")public class ShiroController {        @RequestMapping(value = "/login", method = RequestMethod.GET)        public String login() {                return "login";        }        @RequestMapping(value = "/info", method = RequestMethod.GET)        @RequiresPermissions("sys:config:info")        public String info() {                return "info";        }}

八:登录代码

      @PostMapping("/sys/login")        public Map login(@RequestBody SysLoginForm form)throws IOException {                boolean captcha = captchaService.validate(form.getUuid(), form.getCaptcha());                if(!captcha){                        return R.error("验证码不正确");                }                //用户信息                SysUserEntity user = sysUserService.queryByUserName(form.getUsername());                //账号不存在、密码错误                if(user == null || !user.getPassword().equals(new Sha256Hash(form.getPassword(), user.getSalt()).toHex())) {                        return R.error("账号或密码不正确");                }                //账号锁定                if(Constant.CommonStatus.BANNED.getValue().equals(user.getStatus())){                        return R.error("账号已被锁定,请联系管理员");                }                //生成token,并保存到数据库                R r = sysUserTokenService.createToken(user.getUserId());                return r;        }

登录验证,独立实现,验证成功后创建Token,Token放到Redis缓存中。前端页面请求接口时要带Token.

九:代码调用流程

1.登录成功后,创建Token,后面的接口调用都要传递Token

2.接口首先通过shiroFilter过滤,确定是否要进入OAuth3Filter进行处理

3.OAuth3Filter 首先执行isAccessAllowed方法,如果时OPTIONS请求直接放行,否则进行Shiro的executeLogin验证

4.executeLogin执行的时候会到OAuth3Realm中调用doGetAuthenticationInfo方法,根据Token获取当前登录用户的信息(Token与用户的关联,已在第八点自己实现的登录代码中实现)

5.executeLogin成功后,会看当前访问的接口,有无权限注解@RequiresPermissions

6.如果有注解的话,说需要进行权限验证,Shiro会通过OAuth3Realm的doGetAuthorizationInfo方法,获取当前用户的权限进行验证

以上是"如何使用Spring Boot+Shiro实现权限管理"这篇文章的所有内容,感谢各位的阅读!希望分享的内容对大家有帮助,更多相关知识,欢迎关注行业资讯频道!

0