springboot+shiro框架整合实现前后端分离的权限管理基础demo

蚂蚁舞 蚂蚁舞     2023-03-29     442

关键词:

记录一下使用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+jwt实现认证及权限校验

序言本文讲解如何使用SpringBoot整合Shiro框架来实现认证及权限校验,但如今的互联网已经成为前后端分离的时代,所以本文在使用SpringBoot整合Shiro框架的时候会联合JWT一起搭配使用。ShiroShiro是apache旗下一个开源框架,... 查看详情

springboot+vue+shiro实现前后端分离权限控制

...9813177.html本文总结自实习中对项目的重构。原先项目采用Springboot+freemarker模版,开发过程中觉得前端逻辑写的实在恶心,后端Controller层还必须返回Freemarker模版的ModelAndView,逐渐有了前后端分离的想法,由于之前,没有接触过,... 查看详情

springboot笔记--整合shiro实现前后台分离token鉴权

参考技术A 查看详情

springboot整合shiro+jwt实现认证及权限校验(代码片段)

序言本文讲解如何使用SpringBoot整合Shiro框架来实现认证及权限校验,但如今的互联网已经成为前后端分离的时代,所以本文在使用SpringBoot整合Shiro框架的时候会联合JWT一起搭配使用。ShiroShiro是apache旗下一个开源框架,... 查看详情

springboot+vue+shiro实现前后端分离权限控制

本文总结自实习中对项目的重构。原先项目采用Springboot+freemarker模版,开发过程中觉得前端逻辑写的实在恶心,后端Controller层还必须返回Freemarker模版的ModelAndView,逐渐有了前后端分离的想法,由于之前,没有接触过,主要参考... 查看详情

springboot整合springsecurity示例实现前后分离权限注解

SpringBoot整合SpringSecurity示例实现前后分离权限注解+JWT登录认证 作者:Sans_juejin.im/post/5da82f066fb9a04e2a73daec 一.说明SpringSecurity是一个用于Java企业级应用程序的安全框架,主要包含用户认证和用户授权两个方面.相比较Shiro而言,... 查看详情

前后端分离项目中springboot集成shiro实现权限控制

文章目录​​使用注解控制鉴权授权​​​​使用url配置控制鉴权授权​​​​表结构​​​​jar包依赖​​​​代码说明​​​​身份认证​​​​权限认证​​​​跨域问题解决​​​​登录验证不进行重定向改为设置http... 查看详情

springboot+vue+shiro实现前后端分离,写得太好了!

...9813177.html本文总结自实习中对项目的重构。原先项目采用Springboot+freemarker模版,开发过程中觉得前端逻辑写的实在恶心,后端Controller层还必须返回Freemarker模版的 查看详情

springboot整合vue并实现前后端贯穿调用

一、前言  作为一个后端程序员,前端知识多少还是要了解一些的,vue能很好的实现前后端分离,且更便于我们日常中的调试,还具备了轻量、低侵入性的特点,所以我觉得是很有必要了解的一门前端技术。  这篇文章先记... 查看详情

vue+springboot超详细!一周开发一个springboot+vue+mybatisplus+shiro+jwt+redis前后端分离个人博客项目!!!项目完结(代码片段)

...备前后端分离项目技术栈Java后端接口开发1、前言2、新建Springboot项目3、整合mybatisplus3、统一结果封装4、整合shiro+jwt,并会话共享ShiroConfigAccountRealmJwtTokenAccountProfileJwtFilter5、异常处理6、实体校验7、跨域问题Swagger2配置8、... 查看详情

vue+springboot超详细!一周开发一个springboot+vue+mybatisplus+shiro+jwt+redis前后端分离个人博客项目!!!项目完结(代码片段)

...备前后端分离项目技术栈Java后端接口开发1、前言2、新建Springboot项目3、整合mybatisplus3、统一结果封装4、整合shiro+jwt,并会话共享ShiroConfigAccountRealmJwtTokenAccountProfileJwtFilter5、异常处理6、实体校验7、跨域问题Swagger2配置8、... 查看详情

前后端分离博客项目(springboot/redis/shiro/swagger/mybatis-plus/vue/elementui)

blogs项目blogs项目是myblog项目演进与完善的迭代项目,设计框架由springboot+thymeleaf+mybatis-plus+shiro+swagger改为springboot+vue+mybatis-plus+redis+shiro+swagger的前后端分离项目blogs项目主要由blogs_springboot后端项目与blogs_vue前端项目组成blogs_sprin 查看详情

springboot整合shiro涉及跨域和@cacheable缓存/@transactional事务注解失效问题

...离项目中)  (1) 跨域介绍可参考:跨域(CORS)  (2)SpringBoot中解决跨域方式有:    A.使用@CrossOrigin注解;    B.实现Filter类,重写doFilter方法packagecom.ruhuanxingyun.config;importcn.hutool.core.util.StrUtil;importorg.spri 查看详情

单体物联平台系统(springboot整合shiro实现多realm多用户表多权限表登陆)

单体物联平台系统(Springboot整合shiro实现多realm多用户表多权限表登陆)参考实现:http://www.qchcloud.cn/tn/article/30一、技术框架本项目基于Spring,整合ApacheShiro框架,实现用户管理和权限控制,主要内容如下:1.登录(带验证码),... 查看详情

springboot整合shiro实现权限控制(代码片段)

文章目录1、SpringBoot整合Shiro1.1、shiro简介1.2、代码的具体实现1.2.1、Maven的配置1.2.2、整合需要实现的类1.2.3、项目结构1.2.4、ShiroConfig的实现1.2.5、CustomerRealm的实现1.2.6、shiro缓存配置1.2.7、主页index.html的设置1.3、简单测试1.3.1、adm... 查看详情

springboot整合shiro实现权限控制(代码片段)

文章目录1、SpringBoot整合Shiro1.1、shiro简介1.2、代码的具体实现1.2.1、Maven的配置1.2.2、整合需要实现的类1.2.3、项目结构1.2.4、ShiroConfig的实现1.2.5、CustomerRealm的实现1.2.6、shiro缓存配置1.2.7、主页index.html的设置1.3、简单测试1.3.1、adm... 查看详情

springboot安全管理--整合shiro

...SSM框架中,手动整合Shiro的配置步骤还是比较多的,针对SpringBoot,Shiro官方提供了shiro-spring-boot-web-starter用来简化Shi 查看详情

springboot整合jdbc实现后端项目开发

 一、前言  前后端分离开发是将项目开发工作前后端交互工作拆分,使前端开发人员能够专注于页面开发或APP开发,后端开发人员专注与接口开发、业务逻辑开发。  此处开发后端项目,给前端或者APP端提供接... 查看详情