SpringBoot+Shiro框架整合实现前后端分离的权限管理基础Demo

Posted 蚂蚁舞

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot+Shiro框架整合实现前后端分离的权限管理基础Demo相关的知识,希望对你有一定的参考价值。

记录一下使用SpringBoot集成Shiro框架实现前后端分离Web项目的过程,后端使用SpringBoot整合Shiro,前端使用vue+elementUI,达到前后端使用token来进行交互的应用,这种方式通常叫做无状态,后端只需要使用Shiro框架根据前端传来的token信息授权访问相应资源。

案例源码:SpringBoot+Shiro框架整合实现前后端分离的权限管理基础Demo

首先新建SpringBoot项目,导入Springboot整合shiro所需要的依赖包

<!-- SpringBoot整合shiro所需相关依赖-->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.10.0</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.10.0</version>
</dependency>

<!--web模块的启动器 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

使用的SpringBoot版本

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.5.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

使用SpringBoot集合Shiro之前,需要建立相应的类和从数据库获取的用户数据(这里新建一个java静态类来模拟解决)

用户登录的类UserValidate.java

package boot.example.shiro.domain;

/**
 *  蚂蚁舞
 */
public class UserValidate 

    String username;

    String password;

    // get  set

用户类SysUsers.java

package boot.example.shiro.domain;

/**
 *  蚂蚁舞
 */
public class SysUsers 

    private Integer user_id;

    private String username;

    private String password;

    private int user_type; // 用户类型  -1表示超级账号  1表示普通账号

    private Integer role_id; // 用户角色  拿权限需要的

    private Integer locked;  // 用户状态  1-正常  2=锁定

    public SysUsers() 
    

    public SysUsers(Integer user_id, String username, String password, int user_type, Integer role_id, Integer locked) 
        this.user_id = user_id;
        this.username = username;
        this.password = password;
        this.user_type = user_type;
        this.role_id = role_id;
        this.locked = locked;
    

    // get set

模拟三个用户shiro_admin, myw_admin, app_admin以及相关的方法和静态mock数据

模拟数据库的类ShiroDataMapper.java

package boot.example.shiro.config;

import boot.example.shiro.domain.SysUsers;

import java.util.ArrayList;
import java.util.List;

/**
 *  蚂蚁舞
 */
public class ShiroDataMapper 

    private static final String shiro_admin = "shiro_admin";

    private static final String myw_admin = "myw_admin";

    private static final String app_admin = "app_admin";

    private static final SysUsers sysUsers_shiro_admin = new SysUsers(1, shiro_admin, "123", -1, 1, 1);

    private static final SysUsers sysUsers_myw_admin = new SysUsers(2, myw_admin, "1234", 1, 2, 1);

    private static final SysUsers sysUsers_app_admin = new SysUsers(3, app_admin, "12345",3, 3, 1);

    public static SysUsers getSysUsersByUserName(String username)
        if(username.equalsIgnoreCase(shiro_admin))
            return sysUsers_shiro_admin;
        
        if(username.equalsIgnoreCase(myw_admin))
            return sysUsers_myw_admin;
        
        if(username.equalsIgnoreCase(app_admin))
            return sysUsers_app_admin;
        
        return null;
    

    public static List<String> listSysRolesPermissions(Integer roleId)
        if(roleId == 2)
            List<String> list = new ArrayList<>();
            list.add("sys:user:list");
            list.add("sys:user:update");
            list.add("sys:user:add");
            list.add("sys:user:delete");
            return list;
        
        if(roleId == 3)
            List<String> list = new ArrayList<>();
            list.add("sys:user:list");
            return list;
        
        return null;
    



getSysUsersByUserName方法是用来模拟从数据库获取用户对象数据的,listSysRolesPermissions是根据用户的角色来获取对应的权限列表的。

Shiro框架的ShiroRealm.java

shiro的realm主要用来实现认证(AuthenticationInfo)和授权(AuthorizationInfo)

package boot.example.shiro.config;

import org.apache.shiro.authc.*;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

public class ShiroRealm extends AuthorizingRealm 

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) 
        // to do
    

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException 
        // to do
    


认证的实现,当用户通过接口登录后就会触发这里的认证登录

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException 
    //  获取登录username
    String username = (String)token.getPrincipal();
    //  从数据库获取用户对象 (这里模拟的)
    SysUsers sysUsers = ShiroDataMapper.getSysUsersByUserName(username);
    // 在数据库里没找到用户,异常用户,抛出异常(交给异常处理)
    if(sysUsers == null) 
        throw new UnknownAccountException();    //没找到帐号
    
    // 一般用户允不允许登录也是有一个锁定状态的 从用户对象里拿到锁定状态,判断是否锁定
    if(2 == sysUsers.getLocked()) 
        throw new LockedAccountException();     //帐号锁定
    
    //  交给SimpleAuthenticationInfo去验证密码
    return new SimpleAuthenticationInfo(sysUsers, sysUsers.getPassword(), this.getClass().getName());

授权实现,给超级管理所有权限,给具体的普通用户对应的权限

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) 
    // 获取用户对象
    SysUsers user = (SysUsers)principals.getPrimaryPrincipal();
    // 对象为null 抛出异常
    if(user == null)
        throw new UnknownAccountException();
    
    SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
    SysUsers sysUsers = ShiroDataMapper.getSysUsersByUserName(user.getUsername());
    if(sysUsers == null)
        throw new UnknownAccountException();
    
    // // 用户类型  -1表示超级账号  1表示普通账号
    if(sysUsers.getUser_type() < 0)
        authorizationInfo.addRole("*");  // roles的权限 所有
        authorizationInfo.addStringPermission("*:*:*"); // perms的权限 所有
     else 
        // 用角色id从数据库获取权限列表,这里是模拟的
        List<String> mapList = ShiroDataMapper.listSysRolesPermissions(sysUsers.getRole_id());
        authorizationInfo.addRole("key");
        if (!mapList.isEmpty()) 
            Set<String> permsSet = new HashSet<>();
            for (String perm : mapList) 
                permsSet.addAll(Arrays.asList(perm.trim().split(",")));
            
            authorizationInfo.setStringPermissions(permsSet);
        
    
    return authorizationInfo;

ShiroRealm.java完整代码

package boot.example.shiro.config;


import boot.example.shiro.domain.SysUsers;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * 蚂蚁舞
 */
public class ShiroRealm extends AuthorizingRealm 

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) 
        // 获取用户对象
        SysUsers user = (SysUsers)principals.getPrimaryPrincipal();
        // 对象为null 抛出异常
        if(user == null)
            throw new UnknownAccountException();
        
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        SysUsers sysUsers = ShiroDataMapper.getSysUsersByUserName(user.getUsername());
        if(sysUsers == null)
            throw new UnknownAccountException();
        
        // // 用户类型  -1表示超级账号  1表示普通账号
        if(sysUsers.getUser_type() < 0)
            authorizationInfo.addRole("*");  // roles的权限 所有
            authorizationInfo.addStringPermission("*:*:*"); // perms的权限 所有
         else 
            // 用角色id从数据库获取权限列表,这里是模拟的
            List<String> mapList = ShiroDataMapper.listSysRolesPermissions(sysUsers.getRole_id());
            authorizationInfo.addRole("key");
            if (!mapList.isEmpty()) 
                Set<String> permsSet = new HashSet<>();
                for (String perm : mapList) 
                    permsSet.addAll(Arrays.asList(perm.trim().split(",")));
                
                authorizationInfo.setStringPermissions(permsSet);
            
        
        return authorizationInfo;
    

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException 
        //  获取登录username
        String username = (String)token.getPrincipal();
        //  从数据库获取用户对象 (这里模拟的)
        SysUsers sysUsers = ShiroDataMapper.getSysUsersByUserName(username);
        // 在数据库里没找到用户,异常用户,抛出异常(交给异常处理)
        if(sysUsers == null) 
            throw new UnknownAccountException();    //没找到帐号
        
        // 一般用户允不允许登录也是有一个锁定状态的 从用户对象里拿到锁定状态,判断是否锁定
        if(2 == sysUsers.getLocked()) 
            throw new LockedAccountException();     //帐号锁定
        
        //  交给SimpleAuthenticationInfo去验证密码
        return new SimpleAuthenticationInfo(sysUsers, sysUsers.getPassword(), this.getClass().getName());
    





Shiro框架的ShiroConfig.java

SpringBoot集成Shiro有一个最主要的配置类,这个类里有Shiro框架的会话管理(SessionManager)和安全管理(SecurityManager)和访问过滤器(ShiroFilterFactoryBean)和SpringBoot注解支持和生命周期相关的Bean配置

@Configuration必须加上的!

@Configuration
public class ShiroConfig 

ShiroConfig里首先来配置密码校验的bean

// 密码校验bean
@Bean("credentialMatcher")
public ShiroCredentialMatcher credentialMatcher() 
    return new ShiroCredentialMatcher();

密码校验继承类ShiroCredentialMatcher.java

这里继承了SimpleCredentialsMatcher 实现方式是将登录的密码和数据库查询出来的密码进行一个equals对比,使用这种方式,密码可以是明码进行对比,也可以MD5后的密码,同样的登录密码和数据库内的密码也可以在这里分别经过各自某种加密解密后在对比(安全系数瞬间增强,即使从数据库拿到了密码也没法简单确认出登录密码)

package boot.example.shiro.config;


import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;

/**
 *  蚂蚁舞
 */
public class ShiroCredentialMatcher extends SimpleCredentialsMatcher 

    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) 
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
        String password = new String(usernamePasswordToken.getPassword());
        String dbPassword = (String) info.getCredentials();
        System.out.println("usernamePasswordToken--"+usernamePasswordToken.getUsername()+"--"+password);
        System.out.println("info.getCredentials()-"+info.getCredentials()+"---info.getPrincipals()-"+info.getPrincipals());
        // 密码比对
        return this.equals(password, dbPassword);
    

SimpleCredentialsMatcher的源码

身份认证和权限校验Realm的bean

ShiroRealm就是授权和认证的类,设置的缓存管理使用的是内存,setCredentialsMatcher就是密码校验,MemoryConstrainedCacheManager缓存在内存中(方便快捷)

// 身份认证和权限校验Realm
@Bean("shiroRealm")
public ShiroRealm shiroRealm(@Qualifier("credentialMatcher") ShiroCredentialMatcher matcher)
    ShiroRealm shiroRealm = new ShiroRealm();
    shiroRealm.setCacheManager(new MemoryConstrainedCacheManager());
    shiroRealm.setCredentialsMatcher(matcher);
    return shiroRealm;

SessionManager会话管理

shiro的会话管理SessionManager是用来管理应用中所有 Subject 的会话的创建、维护、删除、失效、验证,有三个默认的实现类

DefaultSessionManager

DefaultWebSessionManager:用于web环境的实现

ServletContainerSessionManager

shiro默认的会话管理是依赖于浏览器的cookie来维持的,也就是说前端代码嵌入到了SpringBoot整合Shiro的环境中,Shiro的会话管理将sesionId 放到 cookie中,现在大多数项目都是前后端分离的,去拿cookie还不如用token机制,一种无状态的机制,在登录的时候获取的token实际上就是shiro的sessionId,如此的话,那么可以继承实现DefaultWebSessionManager类,修改一些需要改变的方法

//  会话管理, 管理用户登录后的会话
@Bean("sessionManager")
public ShiroSessionManager sessionManager()
    //将继承后重写的ShiroSessionManager加入bean
    return new ShiroSessionManager();

token的静态类ShiroConstant.java

package boot.example.shiro.config;
/**
 * 蚂蚁舞
 */
public class ShiroConstant 
    //  定义的请求头中使用的标记key,用来传递 token
    public static final String authorization_token = "token";

重写会话管理类ShiroSessionManager.java

package boot.example.shiro.config;

import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.util.StringUtils;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;

/**
 * 蚂蚁舞
 */
public class ShiroSessionManager extends DefaultWebSessionManager 

    public ShiroSessionManager() 
        super();
        //在这里设置ShiroSession失效时间
        setGlobalSessionTimeout(MILLIS_PER_MINUTE * 15);
    

    @Override
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) 
        //获取请求头中的token值,如果请求头中有token值,则取巧认为其值为会话的sessionId(那么用户在登陆的时候需要给前端传送这个sessionId)
        String sessionId = WebUtils.toHttp(request).getHeader(ShiroConstant.authorization_token);
        System.out.println("sessionId--" + sessionId);
        if (StringUtils.isEmpty(sessionId))
            /**
             * 注意: 在这里有一种特殊情况,那就是不经过shiroFilter过滤器的访问,例如authc认证用户
             * 既然不经过shiroFilter 那么当后端重启清空了会话,可前端依旧把sessionId传给了后端,
             * 出现这种情况,shiro会按照shiroFilterFactoryBean.setLoginUrl("/shiro-redirect/index");设置跳转到登录页面,重新登陆
             * 格式是http://127.0.0.1:20400/shiro-redirect/index;JSESSIONID=04d5ed45-85c1-420b-b7bd-fa622385309f
             * 如果是没有分离的项目,那么直接跳转到了登录页,如果是分离的项目,那就会给前端报出400的错误(这里是整合需要注意的关键点)
             */

            //如果没有携带sessionId的参数,直接按照父类的方式在cookie进行获取sessionId
            return super.getSessionId(request, response);
         else 
            //请求头中如果有token, 则其值为sessionId(登陆的时候就传送这个sessionId)
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "request cookie");
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sessionId); // 这里加上sessionId
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
            return sessionId;
        
    



看看DefaultWebSessionManager父类getSessionId的源码

调用了私有getReferencedSessionId方法

先调用this.getSessionIdCookieValue(request, response)获取sessionId 如果sessionid不存在,则去判断JSESSIONID的参数是不是带有(这个在前后端分离的项目有个大坑,不经过shiroFilter里的访问,接口会报出400错误,ShiroSessionManager的demo代码里有说明),暂时不去分析那么多,前后端分离一般也不会用到类似authc认证用户访问的,一般都是接口访问,有shiroFilter过滤器。


private Serializable getReferencedSessionId(ServletRequest request, ServletResponse response) 
    String id = this.getSessionIdCookieValue(request, response);
    if (id != null) 
        request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "cookie");
     else 
        id = this.getUriPathSegmentParamValue(request, "JSESSIONID");
        if (id == null && request instanceof HttpServletRequest) 
            String name = this.getSessionIdName();
            HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
            String queryString = httpServletRequest.getQueryString();
            if (queryString != null && queryString.contains(name)) 
                id = request.getParameter(name);
            

            if (id == null && queryString != null && queryString.contains(name.toLowerCase())) 
                id = request.getParameter(name.toLowerCase());
            
        

        if (id != null) 
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "url");
        
    

    if (id != null) 
        request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
        request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
    

    request.setAttribute(ShiroHttpServletRequest.SESSION_ID_URL_REWRITING_ENABLED, this.isSessionIdUrlRewritingEnabled());
    return id;

SecurityManager安全管理器 Shiro框架的核心组件

//  安全管理器
@Bean("securityManager")
public SecurityManager securityManager(@Qualifier("shiroRealm") ShiroRealm shiroRealm) 
    // web的安全管理器
    DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
    // 设置授权和认证
    manager.setRealm(shiroRealm);
    // 设置会话管理
    manager.setSessionManager(sessionManager());
    return manager;

ShiroFilterFactoryBean访问过滤器(经常说成是拦截器,实际上是拦截的功能)

//  访问shiro的过滤器
@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager securityManager) 

    ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    shiroFilterFactoryBean.setSecurityManager(securityManager);

    Map<String, Filter> filterMap = new HashMap<>();
    filterMap.put("shiroFilter", new ShiroFilter());
    shiroFilterFactoryBean.setFilters(filterMap);
    // 跳转到登录页,实际跳转后访问的是接口,接口返回请登录的信息
    shiroFilterFactoryBean.setLoginUrl("/shiro-redirect/index");
    //bean.setSuccessUrl("/shiro-redirect/index");
    //  实际跳转到未认证页面,请重新登陆
    shiroFilterFactoryBean.setUnauthorizedUrl("/shiro-redirect/unauthorized");

    LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();

    //  静态路径放开  anon:匿名用户可访问
    filterChainDefinitionMap.put("/public/**", "anon");
    filterChainDefinitionMap.put("/static/**", "anon");

    //  调试工具全部放开    anon:匿名用户可访问
    filterChainDefinitionMap.put("/swagger-resources", "anon");
    filterChainDefinitionMap.put("/swagger-resources/**", "anon");
    filterChainDefinitionMap.put("/v2/api-docs", "anon");
    filterChainDefinitionMap.put("/webjars/**", "anon");
    filterChainDefinitionMap.put("/doc.html", "anon");

    // 登录相关全部放开 anon:匿名用户可访问
    filterChainDefinitionMap.put("/shiro-login/**", "anon");
    filterChainDefinitionMap.put("/shiro-redirect/**", "anon");

    // 匿名用户可访问  anon:匿名用户可访问
    filterChainDefinitionMap.put("/shiro-anon/**", "anon");

    //  认证用户可访问 authc:认证用户可访问
    filterChainDefinitionMap.put("/shiro-authc/*", "authc");

    // 自定义过滤器过滤的内容
    filterChainDefinitionMap.put("/**", "shiroFilter");
    shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

    return shiroFilterFactoryBean;

自定义过滤的类ShiroFilter.java

package boot.example.shiro.config;

import boot.example.shiro.domain.Response;
import boot.example.shiro.domain.ResponseCode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 *  蚂蚁舞
 */
public class ShiroFilter extends BasicHttpAuthenticationFilter 

    //  sendChallenge重写的目的是避免前端在没有登录的情况下访问@RequiresPermissions()等未授权接口返回401错误,
    //  给前端调用接口一个数据,让前端去重新登陆
    //  如果使用浏览器访问,浏览器会弹出一个输入账号密码的弹框,重写后浏览器访问出现接口数据
    protected boolean sendChallenge(ServletRequest request, ServletResponse response) 
        System.out.println("Authentication required: sending 401 Authentication challenge response.");
        HttpServletResponse httpResponse = WebUtils.toHttp(response);
        responseSkip(httpResponse, ResponseCode.noLoginSkipResponse());
        return false;
    

    @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;
        
        //  在配置的ShiroFilterFactoryBean拦截过滤器里,必须使用无状态的token 这里如果没有token 直接告诉前端需要重新登陆
        HttpServletRequest req = (HttpServletRequest) request;
        String authorization = req.getHeader(ShiroConstant.authorization_token);
        if(authorization == null || authorization.length() == 0)
            //  未携带token  不需要提示前端自动跳转重新登陆
            responseSkip(httpServletResponse, ResponseCode.noAuthHeaderTokenResponse("未携带token,请求无效"));
            return false;
        

        //  验证token的正确性
        Subject subject = SecurityUtils.getSubject();
        if(!subject.isAuthenticated())
            //   token失效 提示前端需要自动跳转重新登陆
            responseSkip(httpServletResponse, ResponseCode.invalidHeaderTokenSkipResponse());
            return false;
        

        return super.preHandle(request, response);
    

    private void responseSkip(HttpServletResponse response, Response customizeResponse)
        try 
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8");
            ObjectMapper objectMapper = new ObjectMapper();
            String str = objectMapper.writeValueAsString(customizeResponse);
            response.getWriter().println(str);
         catch (IOException e1) 
            throw new RuntimeException(e1);
        
    


注解支持的bean配置

支持在SpringBoot在Controller使用@RequiresPermission()等标签注解以及配置shiro的生命周期

//  支持在SpringBoot的Controller使用@RequiresPermission()等标签注解 以及
@Bean("authorizationAttributeSourceAdvisor")
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager) 
    AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
    advisor.setSecurityManager(securityManager);
    return advisor;


@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() 
    DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
    // 强制使用cglib,防止重复代理和可能引起代理出错的问题 (没明白)
    defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
    return defaultAdvisorAutoProxyCreator;


//  配置shiro的生命周期处理
@Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() 
    return new LifecycleBeanPostProcessor();

ShiroConfig.java完整类

package boot.example.shiro.config;


import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 蚂蚁舞
 */
@Configuration
public class ShiroConfig 

    // 密码校验bean
    @Bean("credentialMatcher")
    public ShiroCredentialMatcher credentialMatcher() 
        return new ShiroCredentialMatcher();
    

    // 身份认证和权限校验Realm
    @Bean("shiroRealm")
    public ShiroRealm shiroRealm(@Qualifier("credentialMatcher") ShiroCredentialMatcher matcher)
        ShiroRealm shiroRealm = new ShiroRealm();
        shiroRealm.setCacheManager(new MemoryConstrainedCacheManager());
        shiroRealm.setCredentialsMatcher(matcher);
        return shiroRealm;
    

    //  会话管理, 管理用户登录后的会话
    @Bean("sessionManager")
    public ShiroSessionManager sessionManager()
        //将继承后重写的ShiroSessionManager加入bean
        return new ShiroSessionManager();
    

    //  安全管理器
    @Bean("securityManager")
    public SecurityManager securityManager(@Qualifier("shiroRealm") ShiroRealm shiroRealm) 
        // web的安全管理器
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        // 设置授权和认证
        manager.setRealm(shiroRealm);
        // 设置会话管理
        manager.setSessionManager(sessionManager());
        return manager;
    

    //  访问shiro的过滤器
    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager securityManager) 

        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        Map<String, Filter> filterMap = new HashMap<>();
        filterMap.put("shiroFilter", new ShiroFilter());
        shiroFilterFactoryBean.setFilters(filterMap);
        // 跳转到登录页,实际跳转后访问的是接口,接口返回请登录的信息
        shiroFilterFactoryBean.setLoginUrl("/shiro-redirect/index");
        //bean.setSuccessUrl("/shiro-redirect/index");
        //  实际跳转到未认证页面,请重新登陆
        shiroFilterFactoryBean.setUnauthorizedUrl("/shiro-redirect/unauthorized");

        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();

        //  静态路径放开  anon:匿名用户可访问
        filterChainDefinitionMap.put("/public/**", "anon");
        filterChainDefinitionMap.put("/static/**", "anon");

        //  调试工具全部放开    anon:匿名用户可访问
        filterChainDefinitionMap.put("/swagger-resources", "anon");
        filterChainDefinitionMap.put("/swagger-resources/**", "anon");
        filterChainDefinitionMap.put("/v2/api-docs", "anon");
        filterChainDefinitionMap.put("/webjars/**", "anon");
        filterChainDefinitionMap.put("/doc.html", "anon");

        // 登录相关全部放开 anon:匿名用户可访问
        filterChainDefinitionMap.put("/shiro-login/**", "anon");
        filterChainDefinitionMap.put("/shiro-redirect/**", "anon");

        // 匿名用户可访问  anon:匿名用户可访问
        filterChainDefinitionMap.put("/shiro-anon/**", "anon");

        //  认证用户可访问 authc:认证用户可访问
        filterChainDefinitionMap.put("/shiro-authc/*", "authc");

        // 自定义过滤器过滤的内容
        filterChainDefinitionMap.put("/**", "shiroFilter");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        return shiroFilterFactoryBean;
    



    //  支持在SpringBoot的Controller使用@RequiresPermission()等标签注解 以及
    @Bean("authorizationAttributeSourceAdvisor")
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager) 
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    

    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() 
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        // 强制使用cglib,防止重复代理和可能引起代理出错的问题 (没明白)
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    

    //  配置shiro的生命周期处理
    @Bean(name = "lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() 
        return new LifecycleBeanPostProcessor();
    


SpringBoot整合Shiro的web应用需要Controller层来调用测试功能的

首先是ShiroConfig里设置的重定向类

BootShiroIndexRedirectController.java

package boot.example.shiro.controller;


import boot.example.shiro.domain.Response;
import boot.example.shiro.domain.ResponseCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 *  蚂蚁舞
 */
@Controller
@RequestMapping("/shiro-redirect")
public class BootShiroIndexRedirectController 

    public Logger log = LoggerFactory.getLogger(this.getClass());

    @RequestMapping("/index")
    @ResponseBody
    public Response index() 
        log.warn("redirect index");
        return ResponseCode.noLoginResponse();
    

    @RequestMapping("/unauthorized")
    @ResponseBody
    public Response unauthorized() 
        log.warn("redirect unauthorized");
        return ResponseCode.unauthorizedPermissionResponse();
    



匿名游客访问类BootShiroTestAnonController.java

package boot.example.shiro.controller;

import boot.example.shiro.domain.Response;
import boot.example.shiro.domain.ResponseCode;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 蚂蚁舞
 */
@RestController
@RequestMapping(value="/shiro-anon")
public class BootShiroTestAnonController 

    @GetMapping(value="/hello")
    public Response anonHello() 
        return ResponseCode.successResponse("匿名游客用户可访问");
    

已经认证也就是登录的用户访问类BootShiroTestAuthcController.java

package boot.example.shiro.controller;

import boot.example.shiro.domain.Response;
import boot.example.shiro.domain.ResponseCode;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(value="/shiro-authc")
public class BootShiroTestAuthcController 

    @GetMapping(value="/hello")
    public Response authCHello() 
        return ResponseCode.successResponse("你是认证用户,可访问此接口");
    

使用权限注解的类BootShiroTestSysUserController.java

package boot.example.shiro.controller;

import boot.example.shiro.domain.Response;
import boot.example.shiro.domain.ResponseCode;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.*;

/**
 *  蚂蚁舞
 */
@RestController
@RequestMapping(value="/sysUser")
public class BootShiroTestSysUserController 

    @GetMapping(value="/hello")
    public Response shiroFilterHello() 
        return ResponseCode.successResponse("你正在访问登录后shiroFilter过滤器里的,无注解的接口");
    

    @RequiresPermissions("sys:user:list")
    @GetMapping(value="/list")
    @ResponseBody
    public Response userList() 
        return ResponseCode.successResponse("你已经成功访问到查询用户接口");
    

    @RequiresPermissions("sys:user:add")
    @GetMapping(value="/insert")
    @ResponseBody
    public Response userAdd() 
        return ResponseCode.successResponse("你已经成功访问到新增用户接口");
    

    @RequiresPermissions("sys:user:update")
    @GetMapping(value="/update")
    @ResponseBody
    public Response userUpdate() 
        return ResponseCode.successResponse("你已经成功访问到更新用户接口");
    

    @RequiresPermissions("sys:user:delete")
    @GetMapping(value="/delete")
    @ResponseBody
    public Response userDelete() 
        return ResponseCode.successResponse("你已经成功访问到删除用户接口");
    




登出类BootShiroLogoutController.java

package boot.example.shiro.controller;


import boot.example.shiro.domain.Response;
import boot.example.shiro.domain.ResponseCode;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 *  蚂蚁舞
 */
@Controller
@RequestMapping("/shiro-logout")
public class BootShiroLogoutController 

    @GetMapping(value="/logout")
    @ResponseBody
    public Response logoutGet() 
        Subject subject = SecurityUtils.getSubject();
        if(subject != null)
            subject.logout();
            return ResponseCode.successResponse("登出成功");
        
        return ResponseCode.failResponse("登出失败");
    

    @PostMapping(value="/logout")
    @ResponseBody
    public Response logoutPost() 
        Subject subject = SecurityUtils.getSubject();
        if(subject != null)
            subject.logout();
            return ResponseCode.successResponse("登出成功");
        
        return ResponseCode.failResponse("登出失败");
    



登录使用的类BootShiroLoginController.java

package boot.example.shiro.controller;

import boot.example.shiro.domain.Response;
import boot.example.shiro.domain.ResponseCode;
import boot.example.shiro.domain.SysUsers;
import boot.example.shiro.domain.UserValidate;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.Map;

/**
 *  蚂蚁舞
 */
@Controller
@RequestMapping("/shiro-login")
public class BootShiroLoginController 

    @GetMapping(value="/auth")
    @ResponseBody
    public Response authGet(@RequestParam(value = "username", required = true, defaultValue="shiro_admin") String username, @RequestParam(value = "password", required = true, defaultValue="123") String password) 
        UserValidate userValidate = new UserValidate();
        userValidate.setPassword(password);
        userValidate.setUsername(username);
        UsernamePasswordToken token = new UsernamePasswordToken(userValidate.getUsername(), userValidate.getPassword());
        Subject subject = SecurityUtils.getSubject();

        try 
            subject.login(token);
            SysUsers sysUsers = (SysUsers) subject.getPrincipal();
            Map<String, Object> map = new HashMap<>();
            map.put("token", subject.getSession().getId().toString());
            map.put("session", subject.getSession());
            map.put("sysUsers", sysUsers);
            return ResponseCode.successResponse(map);
         catch ( UnknownAccountException uae ) 
            return ResponseCode.failResponse("error username");
         catch ( IncorrectCredentialsException ice ) 
            return ResponseCode.failResponse("error password");
         catch ( LockedAccountException lae ) 
            return ResponseCode.failResponse("locked user");
        
    

    @PostMapping(value="/auth")
    @ResponseBody
    public Response authPost(@RequestBody UserValidate userValidate, HttpSession session) 
        System.out.println(userValidate.toString());
        UsernamePasswordToken token = new UsernamePasswordToken(userValidate.getUsername(), userValidate.getPassword());
        Subject subject = SecurityUtils.getSubject();
        try 
            subject.login(token);
            SysUsers sysUsers = (SysUsers) subject.getPrincipal();
            Map<String, Object> map = new HashMap<>();
            map.put("token", subject.getSession().getId().toString());
            map.put("session", subject.getSession());
            map.put("sysUsers", sysUsers);
            return ResponseCode.successResponse(map);
         catch ( UnknownAccountException uae ) 
            return ResponseCode.failResponse("error username");
         catch ( IncorrectCredentialsException ice ) 
            return ResponseCode.failResponse("error password");
         catch ( LockedAccountException lae ) 
            return ResponseCode.failResponse("locked user");
        
    

SpringBoot整合Shiro的代码里有抛出异常的情况,主要的异常在登录的时候会在try catch里处理,返回给前端,但还是有些异常是捕获不到的,因此需要加上异常处理

import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.authz.UnauthorizedException;

// shiro 未授权异常
@ExceptionHandler(UnauthorizedException.class)
@ResponseBody
public Response UnauthorizedExceptionHandler(HttpServletRequest request, UnauthorizedException e) 
    log.error(request.getRequestURI()+"----"+e.toString());
    return ResponseCode.unauthorizedPermissionResponse("未授权,您的操作权限不够,可联系管理员获取操作权限");


//  shiro 授权异常
@ExceptionHandler(AuthorizationException.class)
@ResponseBody
public Response AuthorizationException(HttpServletRequest request, AuthorizationException e) 
    log.error(request.getRequestURI()+"----"+e.toString());
    return ResponseCode.failResponse( "授权用户不存在或已经过期,请重新登录");


//  shiro 未经身份验证或身份验证异常
@ExceptionHandler(UnauthenticatedException.class)
@ResponseBody
public Response UnauthenticatedException(HttpServletRequest request, UnauthenticatedException e) 
    log.error(request.getRequestURI()+"----"+e.toString());
    return ResponseCode.failResponse("未经身份验证,身份验证异常,请重新登录");


//  shiro 账号锁定异常
@ExceptionHandler(LockedAccountException.class)
@ResponseBody
public Response LockedAccountException(HttpServletRequest request, LockedAccountException e) 
    log.error(request.getRequestURI()+"----"+e.toString());
    return ResponseCode.failResponse("你的账号已锁定,请联系管理员解锁");


//  shiro 未找到用户异常
@ExceptionHandler(UnknownAccountException.class)
@ResponseBody
public Response UnknownAccountException(HttpServletRequest request, UnknownAccountException e) 
    log.error(request.getRequestURI()+"----"+e.toString());
    return ResponseCode.failResponse("你的账号不存在");


//  shiro 登录用户密码校验异常
@ExceptionHandler(IncorrectCredentialsException.class)
@ResponseBody
public Response IncorrectCredentialsException(HttpServletRequest request, IncorrectCredentialsException e) 
    log.error(request.getRequestURI()+"----"+e.toString());
    return ResponseCode.failResponse("你输入的密码错误");

完整的异常处理类GlobalExceptionHandler.java

package boot.example.shiro.config;


import boot.example.shiro.domain.Response;
import boot.example.shiro.domain.ResponseCode;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.authz.permission.InvalidPermissionStringException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.bind.BindException;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;


/**
 * 蚂蚁舞
 */
@ControllerAdvice
public class GlobalExceptionHandler 

    public Logger log = LoggerFactory.getLogger(this.getClass());

    //  全局异常:默认异常
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public Response defaultExceptionHandler(HttpServletRequest request, Exception e) 
        log.error(request.getRequestURI()+"----"+e.toString());
        return ResponseCode.exceptionResponse(request.getRequestURI()+e.toString());
    

    @ExceptionHandler(BindException.class)
    @ResponseBody
    public Response bindExceptionHandler(HttpServletRequest request, BindException e) 
        return ResponseCode.exceptionResponse(e.toString());
    


    //  全局异常:请求header缺少HeaderToken
    @ExceptionHandler(ServletRequestBindingException.class)
    @ResponseBody
    public Response ServletRequestBindingExceptionHandler(HttpServletRequest request, ServletRequestBindingException e) 
        log.error(request.getRequestURI()+"----"+e.toString());
        return ResponseCode.noAuthHeaderTokenResponse();
    

    //  全局异常:请求内容类型异常
    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    @ResponseBody
    public Response HttpMediaTypeNotSupportedExceptionHandler(HttpServletRequest request, HttpMediaTypeNotSupportedException e) 
        log.error(request.getRequestURI()+"----"+e.toString());
        return ResponseCode.exceptionResponse(e.toString());
    

    //  全局异常:请求方法异常
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    @ResponseBody
    public Response HttpRequestMethodNotSupportedExceptionHandler(HttpServletRequest request, HttpRequestMethodNotSupportedException e) 
        log.error(request.getRequestURI() +"----"+e.toString());
        return ResponseCode.exceptionResponse(e.toString());
    

    //  全局异常:请求参数格式或者参数类型不正确异常
    @ExceptionHandler(HttpMessageNotReadableException.class)
    @ResponseBody
    public Response HttpMessageNotReadableExceptionHandler(HttpServletRequest request, HttpMessageNotReadableException e) 
        log.error(request.getRequestURI()+"----"+e.toString());
        return ResponseCode.exceptionResponse(e.toString());
    

    //  shiro 权限不可用
    @ExceptionHandler(InvalidPermissionStringException.class)
    @ResponseBody
    public Response InvalidPermissionStringException(HttpServletRequest request, IncorrectCredentialsException e) 
        log.error(request.getRequestURI()+"----"+e.toString());
        return ResponseCode.notPermissionResponse("你的权限不可用");
    

    // shiro 未授权异常
    @ExceptionHandler(UnauthorizedException.class)
    @ResponseBody
    public Response UnauthorizedExceptionHandler(HttpServletRequest request, UnauthorizedException e) 
        log.error(request.getRequestURI()+"----"+e.toString());
        return ResponseCode.unauthorizedPermissionResponse("未授权,您的操作权限不够,可联系管理员获取操作权限");
    

    //  shiro 授权异常
    @ExceptionHandler(AuthorizationException.class)
    @ResponseBody
    public Response AuthorizationException(HttpServletRequest request, AuthorizationException e) 
        log.error(request.getRequestURI()+"----"+e.toString());
        return ResponseCode.failResponse( "授权用户不存在或已经过期,请重新登录");
    

    //  shiro 未经身份验证或身份验证异常
    @ExceptionHandler(UnauthenticatedException.class)
    @ResponseBody
    public Response UnauthenticatedException(HttpServletRequest request, UnauthenticatedException e) 
        log.error(request.getRequestURI()+"----"+e.toString());
        return ResponseCode.failResponse("未经身份验证,身份验证异常,请重新登录");
    

    //  shiro 账号锁定异常
    @ExceptionHandler(LockedAccountException.class)
    @ResponseBody
    public Response LockedAccountException(HttpServletRequest request, LockedAccountException e) 
        log.error(request.getRequestURI()+"----"+e.toString());
        return ResponseCode.failResponse("你的账号已锁定,请联系管理员解锁");
    

    //  shiro 未找到用户异常
    @ExceptionHandler(UnknownAccountException.class)
    @ResponseBody
    public Response UnknownAccountException(HttpServletRequest request, UnknownAccountException e) 
        log.error(request.getRequestURI()+"----"+e.toString());
        return ResponseCode.failResponse("你的账号不存在");
    

    //  shiro 登录用户密码校验异常
    @ExceptionHandler(IncorrectCredentialsException.class)
    @ResponseBody
    public Response IncorrectCredentialsException(HttpServletRequest request, IncorrectCredentialsException e) 
        log.error(request.getRequestURI()+"----"+e.toString());
        return ResponseCode.failResponse("你输入的密码错误");
    




跨域支持的BeanConfig.java

package boot.example.shiro.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

/**
 *  蚂蚁舞
 */
@Configuration
public class BeanConfig 
    @Bean
    public CorsFilter corsFilter()
        UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowCredentials(true);
        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("PUT");
        corsConfiguration.addAllowedMethod("GET");
        corsConfiguration.addAllowedMethod("POST");
        corsConfiguration.addAllowedMethod("PATCH");
        corsConfiguration.addAllowedMethod("OPTIONS");
        corsConfiguration.addAllowedMethod("DELETE");
        corsConfiguration.setMaxAge(1728000L);
        urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
        return new CorsFilter(urlBasedCorsConfigurationSource);
    



Response和静态类ResponseCode是统一封装的result结果集

public class Response 

    private boolean state;

    private int code;

    private String msg;

    private Object data;

    private long timestamp;

    // get set

完整的SpringBoot整合Shiro的代码结构

└─boot-example-shiro-separate-2.0.5
│ pom.xml
│
├─doc
│ boot-example-shiro-separate-2.0.5-back.zip
│
└─src
├─main
│ ├─java
│ │ └─boot
│ │ └─example
│ │ └─shiro
│ │ │ ShiroApp.java
│ │ │
│ │ ├─config
│ │ │ BeanConfig.java
│ │ │ GlobalExceptionHandler.java
│ │ │ ShiroConfig.java
│ │ │ ShiroConstant.java
│ │ │ ShiroCredentialMatcher.java
│ │ │ ShiroDataMapper.java
│ │ │ ShiroFilter.java
│ │ │ ShiroRealm.java
│ │ │ ShiroSessionManager.java
│ │ │ SwaggerConfig.java
│ │ │
│ │ ├─controller
│ │ │ BootShiroIndexRedirectController.java
│ │ │ BootShiroLoginController.java
│ │ │ BootShiroLogoutController.java
│ │ │ BootShiroTestAnonController.java
│ │ │ BootShiroTestAuthcController.java
│ │ │ BootShiroTestSysUserController.java
│ │ │
│ │ └─domain
│ │ Response.java
│ │ ResponseCode.java
│ │ SysUsers.java
│ │ UserValidate.java
│ │
│ └─resources
│ application.properties
│ logback-spring.xml
│
└─test
└─java
└─boot
└─example
└─shiro
ShiroAppTest.java

启动SpringBoot项目,访问swagger-ui(实际前后端分离的情况,这种方式也适合将前端代码嵌入到SpringBoot项目中)

http://localhost:20400/doc.html

浏览器和SwaggerUi测试

1.先不登录,访问匿名游客(anon)

2.先不登录,访问认证用户可访问(authc)这里有重定向

在浏览器直接访问/shiro-authc/hello 因为没有授权重定向到了 /shiro-redirect/index,但在接口上就看不到重定向操作了,直接得到数据未登录的结果

3.登录,使用预定的账号访问带注解的接口

app_admin 12345
这个账号登录访问的接口权限只有sys:user:list
也就是只能访问@RequiresPermissions("sys:user:list")

前后端分离 springboot整合shiro

  1. 实现前后端的跨域,我是在后端配置类里实现
  2. 创建配置类 WebMvcConfig
  3. import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.CorsRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    // 配置全局跨域
    @Configuration
    public class WebMvcConfig implements WebMvcConfigurer {
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping("/**")
                    .allowedOrigins("http://localhost:8081")
                    .allowedMethods("*")
                    .allowedHeaders("*")
                    .allowCredentials(true);
        }
    }
  4. Shiro的配置
  5. 创建配置类 ShiroConfig
  6. import cn.xej.util.MyPassThruAuthenticationFilter;
    import cn.xej.util.MyRealm;
    import cn.xej.util.MySessionManager;
    import java.util.HashMap;
    import java.util.LinkedHashMap;
    import java.util.Map;
    import javax.servlet.Filter;
    import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
    import org.apache.shiro.mgt.SecurityManager;
    import org.apache.shiro.session.mgt.SessionManager;
    import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
    import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    
    @Configuration
    public class ShiroConfig {
    
        /**
         * 请求拦截
         * @param securityManager
         * @return
         */
        @Bean
        public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            shiroFilterFactoryBean.setSecurityManager(securityManager);
            Map<String, Filter> filtersMap = new HashMap<>();
            MyPassThruAuthenticationFilter authFilter = new MyPassThruAuthenticationFilter();
            filtersMap.put("authc", authFilter);
            shiroFilterFactoryBean.setFilters(filtersMap);
            // 拦截器.
            Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
            filterChainDefinitionMap.put("/doLogin", "anon");
            filterChainDefinitionMap.put("/**", "authc");
    //        shiroFilterFactoryBean.setLoginUrl("/unauth");
    
            shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
            return shiroFilterFactoryBean;
        }
    
        /**
         * @Title: securityManager
         * @Description: SecurityManager,权限管理,这个类组合了登陆,登出,权限,session的处理
         * @return SecurityManager
         */
        @Bean
        public SecurityManager securityManager() {
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            securityManager.setRealm(myRealm());
            securityManager.setSessionManager(sessionManager());
            return securityManager;
        }
    
        /**
         * 自定义认证
         * @Title: myShiroRealm
         * @Description: ShiroRealm,这是个自定义的认证类,继承自AuthorizingRealm,负责用户的认证和权限的处理
         * @return MyShiroRealm
         */
        @Bean
        public MyRealm myRealm() {
            MyRealm myRealm = new MyRealm();
            myRealm.setCredentialsMatcher(hashedCredentialsMatcher());
            return myRealm;
        }
    
        /**
         * 密码凭证匹配器,作为自定义认证的基础 (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了 )
         *
         * @return
         */
        @Bean
        public HashedCredentialsMatcher hashedCredentialsMatcher() {
            HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
            hashedCredentialsMatcher.setHashAlgorithmName("MD5");// 散列算法:这里使用MD5算法;
            hashedCredentialsMatcher.setHashIterations(2);// 散列的次数,比如散列两次,相当于 md5(md5(""));
            hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
            return hashedCredentialsMatcher;
        }
    
        /**
         * 自定义sessionManager,用户的唯一标识,即Token或Authorization的认证
         */
        @Bean
        public SessionManager sessionManager() {
            MySessionManager mySessionManager = new MySessionManager();
            return mySessionManager;
        }
    
    }
    
  7. 创建MySessionManager类
  8. import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
    import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
    import org.apache.shiro.web.util.WebUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.util.StringUtils;
    
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import java.io.Serializable;
    
    
    public class MySessionManager extends DefaultWebSessionManager{
    
        private Logger logger = LoggerFactory.getLogger(this.getClass());
        private static final String AUTHORIZATION = "Authorization";
        private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
    
        public MySessionManager() {
            super();
        }
    
        @Override
        public Serializable getSessionId(ServletRequest request, ServletResponse response) {
            //前端ajax的headers中必须传入Authorization的值
            String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
            //如果请求头中有 Authorization 则其值为sessionId
            if (!StringUtils.isEmpty(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);
                logger.info("sessionId="+id);
                return id;
            } else {
                //否则按默认规则从cookie取sessionId
                logger.info("sessionID为"+id);
                return super.getSessionId(request, response);
            }
        }
    }  
  9. 过滤器MyPassThruAuthenticationFilter
  10. import org.apache.shiro.web.filter.authc.PassThruAuthenticationFilter;
    import org.apache.shiro.web.util.WebUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.web.bind.annotation.RequestMethod;
    
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    
    public class MyPassThruAuthenticationFilter extends PassThruAuthenticationFilter{
        private Logger log = LoggerFactory.getLogger(this.getClass());
    
        //获取请求方法,若为OPTIONS请求直接返回True放行
        @Override
        public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
    
            HttpServletRequest req = (HttpServletRequest) request;
            HttpServletResponse res = (HttpServletResponse) response;
            if (req.getMethod().equals(RequestMethod.OPTIONS.name())) {
                log.info("OPTIONS方法直接返回True");
                return true;
            }
            return super.onPreHandle(request, response, mappedValue);
        }
    
    
        @Override
        protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
            HttpServletResponse httpResp = WebUtils.toHttp(response);
            HttpServletRequest httpReq = WebUtils.toHttp(request);
    
            /** 系统重定向会默认把请求头清空,这里通过拦截器重新设置请求头,解决跨域问题 */
            httpResp.addHeader("Access-Control-Allow-Origin", httpReq.getHeader("Origin"));
            httpResp.addHeader("Access-Control-Allow-Headers", "*");
            httpResp.addHeader("Access-Control-Allow-Methods", "*");
            httpResp.addHeader("Access-Control-Allow-Credentials", "true");
    
            if (isLoginRequest(request, response)) {
                return true;
            } else {
                saveRequestAndRedirectToLogin(request, response);
                return false;
            }
        }
    
    } 
  11. 前端我是使用jquery
  12. $.ajax({
                url: base + ‘/doLogin‘,
                type: "post",
                data: {
                    username: username,
                    password: password
                },
                dataType: ‘json‘,
                success: function (data) {
                    if (data.status === 200) {
                        alert(data.message);
                        sessionStorage.setItem("sessionId",data.data);
                        window.location = ‘main.html‘;
                    } else {
                        alert("失败");
                        location.reload();
                    }
                }
            }) 
  13. $.ajax({
        type: "GET",
        url: base + ‘/main‘,
        dataType: ‘json‘,
        beforeSend: function (xhr) {
          xhr.setRequestHeader("Authorization",sessionId)
        },
        success: function (data) {
          if(data.status === 200) {
            $("#info").html(data.message);
          }
        }
    })   
  14. 前面的beforeSend是设置请求头,一定要加。
  15. 后台控制器用户登录
  16. @PostMapping("/doLogin")
        public BaseResult doLogin(HttpServletRequest request, @RequestParam("username")String username, @RequestParam("password")String password){
            System.out.println(username);
            System.out.println(password);
    
            Map<String, String> map = new HashMap<String, String>();
            Subject subject = SecurityUtils.getSubject();
            UsernamePasswordToken token = new UsernamePasswordToken(username,password);
    
            try {
                subject.login(token);
                System.out.println(subject.getSession().getId());
                String sessionId = (String)subject.getSession().getId();
                System.out.println("777" + subject.getSession().getId());
    
                return BaseResult.success("登录成功!",sessionId);
            }catch (UnknownAccountException e){
                System.out.println("账号不存在");
                return BaseResult.fail("失败");
            }catch (AccountException e){
                System.out.println("密码错误");
                return BaseResult.fail("失败");
            }
        } 
  17. @GetMapping("/main")
        public BaseResult Main(HttpServletRequest request, HttpServletResponse response){
            System.out.println("***********");
    
            MySessionManager mySessionManager = new MySessionManager();
            Serializable sessionId = (Serializable) mySessionManager.getSessionId(request,response);
            System.out.println(sessionId);
    
            Subject requestSubject = new Subject.Builder().sessionId(sessionId).buildSubject();
            User user = (User) requestSubject.getPrincipal();
            List<Role> roleList = userService.getRoleByUserId(user.getUid());
            List<Permission> permissionList = null;
            
            for (Role r : roleList){
                System.out.println(r.getRoleName());
                System.out.println("角色id为:"+r.getRid());
                permissionList = permissionService.getPermissionByRoleId(r.getRid());   
            }
            return BaseResult.success("成功",permissionList);
    
        }
  18. 前后端跨域,以及整合shiro好了。

 

 

 

  

  

 

 

 

以上是关于SpringBoot+Shiro框架整合实现前后端分离的权限管理基础Demo的主要内容,如果未能解决你的问题,请参考以下文章

SpringBoot整合Shiro+JWT实现认证及权限校验

SpringBoot整合Shiro+JWT实现认证及权限校验

Spring Boot整合Shiro实现前后端分离

spring boot2整合shiro安全框架实现前后端分离的JWT token登录验证

SpringBoot笔记--整合Shiro实现前后台分离Token鉴权

Vue+SpringBoot超详细!一周开发一个SpringBoot + Vue+MybatisPlus+Shiro+JWT+Redis前后端分离个人博客项目!!!项目完结