您现在的位置是:首页 >技术教程 >SpringBoot+Shiro+Jwt+Vue+elementUI实现前后端分离单体系统Demo网站首页技术教程

SpringBoot+Shiro+Jwt+Vue+elementUI实现前后端分离单体系统Demo

蚂蚁舞 2023-06-15 00:00:03
简介SpringBoot+Shiro+Jwt+Vue+elementUI实现前后端分离单体系统Demo

记录一下使用SpringBoot集成Shiro框架和Jwt框架实现前后端分离Web项目的过程,后端使用SpringBoot整合Shiro+Jwt(auth0),前端使用vue+elementUI框架,前后端的交互使用的是jwt的token,shiro的会话关闭,后端只需要使用Shiro框架根据前端传来的jwt的token信息授权访问相应资源。
pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

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

    <groupId>boot.example.shiro.auth0</groupId>
    <artifactId>boot-example-shiro-separate-auth0-jwt-2.0.5</artifactId>
    <name>boot-example-shiro-separate-auth0-jwt-2.0.5</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>

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

        <!-- 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>

        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>4.2.1</version>
        </dependency>

        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>swagger-bootstrap-ui</artifactId>
            <version>1.9.2</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <!-- 这个插件可以将应用打包成一个可执行的jar包 -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

JwtUtils.java 提供jwt的创建和验证

package boot.shiro.auth0.config;


import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTCreationException;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;

import java.util.Calendar;
import java.util.Date;

/**
 *  蚂蚁舞
 */
public class JwtUtils {
    //  过期时间
    private static final long ExpireTime = 2*60*60*1000;
    //  发布者
    public static final String Issuer = "example";
    //  数据
    public static final String Claim = "claim";
    //  密钥
    public static final String Secret = "12345678";

    public static String createToken(String authStr){
        try{
            long now = System.currentTimeMillis();
            Date issuedAt = new Date(now);
            Date expiresAt = new Date(now + ExpireTime);
            Algorithm algorithm = Algorithm.HMAC256(Secret);
            return JWT.create()
                    .withIssuer(Issuer)                        // 发布者
                    .withIssuedAt(issuedAt)                    // 生成签名的时间
                    .withExpiresAt(expiresAt)                  // 生成签名的有效期,小时
                    .withClaim(Claim, authStr)                 // 插入数据
                    .sign(algorithm);
        } catch (JWTCreationException e){
            //e.printStackTrace();
            //如果Claim不能转换为JSON,或者在签名过程中使用的密钥无效,那么将会抛出JWTCreationException异常
        }
        return null;
    }

    public static boolean verifyToken(String token){
        try {
            String claim = getClaimData(token);
            Algorithm algorithm = Algorithm.HMAC256(Secret);
            JWTVerifier verifier = JWT.require(algorithm)
                    .withIssuer(Issuer)         //匹配指定的发布者
                    .withClaim(Claim,claim)     //数据
                    .build();
            DecodedJWT jwt = verifier.verify(token); //解码JWT ,verifier 可复用
//            System.out.println(jwt.getToken());
//            System.out.println(jwt.getHeader());
//            System.out.println(jwt.getPayload());
//            System.out.println(jwt.getSignature());
            return true;
        }catch (JWTVerificationException e){
            //e.printStackTrace();
            return false;
        }
    }

    public static String getClaimData(String token){
        try{
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim(Claim).asString();
        } catch (JWTDecodeException e){
            //e.printStackTrace();
            return null;
        }
    }

    public static boolean isTokenExpiresAt(String token){
        try {
            Date now = Calendar.getInstance().getTime();
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getExpiresAt().before(now); // before 过期了true
        } catch (JWTDecodeException e) {
            //e.printStackTrace();
            return true;  // 解析出现异常,这里默认true,默认把异常认为该token过期
        }
    }

}

JwtRealm.java 这里关键点supports方法

package boot.shiro.auth0.config;

import boot.shiro.auth0.domain.UserEntity;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationException;
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.Component;

import java.util.*;

/**
 *  蚂蚁舞
 */
@Component
public class JwtRealm extends AuthorizingRealm {

    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }

    //  授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("doGetAuthorizationInfo");
        // 获取当前用户
        UserEntity user = (UserEntity) SecurityUtils.getSubject().getPrincipal();
        //UserEntity user = (UserEntity) principals.getPrimaryPrincipal();
        if(user == null){
            throw new AuthorizationException("需要授权的用户不存在或已经过期");
        }
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        UserEntity entity = JwtDataMapper.getSysUsersByUserName(user.getUsername());
        if(entity == null){
            throw new UnknownAccountException();
        }
        if(entity.getUsername().equalsIgnoreCase(JwtDataMapper.shiro_admin)){
            authorizationInfo.addRole("*");  // roles的权限 所有
            authorizationInfo.addStringPermission("*:*:*"); // perms的权限 所有
        } else {
            // 用角色id从数据库获取权限列表,这里是模拟的
            List<String> mapList = JwtDataMapper.listSysRolesPermissions(user.getRoleId());
            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 {
        System.out.println("doGetAuthenticationInfo");
        JwtToken jwtToken = (JwtToken) token;
        if (jwtToken.getPrincipal() == null) {
            throw new AccountException("username不存在");
        }
        // 从 JwtToken 中获取当前用户
        String jwt_token = jwtToken.getCredentials().toString();
        String username = JwtUtils.getClaimData(jwt_token);
        if(username == null){
            throw new AccountException("username不存在");
        }
        //  从数据库获取用户对象 (这里模拟的)
        UserEntity user = JwtDataMapper.getSysUsersByUserName(username);
        // 在数据库里没找到用户,异常用户,抛出异常(交给异常处理)
        if(user == null) {
            throw new UnknownAccountException();    //没找到帐号
        }
        // 一般用户允不允许登录也是有一个锁定状态的 从用户对象里拿到锁定状态,判断是否锁定
        if(user.getLocked()) {
            throw new LockedAccountException();     //帐号锁定
        }
        return new SimpleAuthenticationInfo(user, jwt_token, this.getClass().getName());
    }



}


JwtToken.java这里Principal和Credentials都是token

package boot.shiro.auth0.config;

import org.apache.shiro.authc.AuthenticationToken;

/**
 * 蚂蚁舞
 */
public class JwtToken implements AuthenticationToken {

    //加密后的 JWT token
    private String token;

    public JwtToken(String token) {
        this.token = token;
    }

    @Override
    public Object getPrincipal() {
        return this.token;
    }

    @Override
    public Object getCredentials() {
        return token;
    }

    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }
}

ShiroConfig.java配置类

package boot.shiro.auth0.config;


import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
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("shiroRealm")
    public JwtRealm shiroRealm(){
        JwtRealm jwtRealm = new JwtRealm();
        jwtRealm.setCachingEnabled(true);
        //启用身份验证缓存,即缓存AuthenticationInfo信息,默认是false
        jwtRealm.setAuthenticationCachingEnabled(true);
        //缓存AuthenticationInfo信息的缓存名称
        jwtRealm.setAuthenticationCacheName("authenticationCache");
        //启用授权缓存,即缓存AuthorizationInfo信息,默认是false
        jwtRealm.setAuthorizationCachingEnabled(true);
        //缓存AuthorizationInfo信息的缓存名称
        jwtRealm.setAuthorizationCacheName("authorizationCache");
        jwtRealm.setCacheManager(new MemoryConstrainedCacheManager());
        return jwtRealm;
    }

    @Bean("securityManager")
    public SecurityManager securityManager(@Qualifier("shiroRealm") JwtRealm shiroRealm) {
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        defaultWebSecurityManager.setRealm(shiroRealm);
        defaultWebSecurityManager.setCacheManager(new MemoryConstrainedCacheManager());
        // 关闭shiro自带的session
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        defaultWebSecurityManager.setSubjectDAO(subjectDAO);

        return defaultWebSecurityManager;
    }

    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager manager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(manager);

        Map<String, Filter> filterMap = new HashMap<>();
        // 两种过滤器方式,使用其中一种就可以
        //filterMap.put("shiroFilter", new ShiroFilter());
        filterMap.put("shiroFilter", new ShiroFilter2());
        bean.setFilters(filterMap);
        bean.setLoginUrl("/shiro-redirect/index");  //  // 跳转到登录页,实际跳转后访问的是接口,接口返回请登录的信息
        bean.setUnauthorizedUrl("/shiro-redirect/unauthorized");    //  实际跳转到未认证页面,请重新登陆

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

        //  静态路径放开
        filterChainDefinitionMap.put("/public/**", "anon");
        filterChainDefinitionMap.put("/static/**", "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");

        // 登录相关全部放开
        filterChainDefinitionMap.put("/shiro-login/**", "anon");
        filterChainDefinitionMap.put("/shiro-redirect/**", "anon");

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

        // 自定义拦截的内容
        filterChainDefinitionMap.put("/**", "shiroFilter");
        bean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        return bean;
    }


    @Bean
    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();
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }

    @Bean(name = "lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }
}

过滤器的两种方案 ShiroFilter.java

package boot.shiro.auth0.config;


import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
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 {

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        System.out.println("isAccessAllowed:");
        //当header带有验证信息,那么验证登陆
        if (isLoginAttempt(request, response)) {
            //如果存在,则进入 executeLogin 方法,检查 token 是否正确
            if(executeLogin(request, response)){
                return true;
            }
            invalidTokenRedirect(response);
        } else {
            noAuthTokenRedirect(response);
        }
        return false;
    }

    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        System.out.println("isLoginAttempt");
        HttpServletRequest req = (HttpServletRequest) request;
        String authorization = req.getHeader(JwtConstant.authorization_token);
        if(authorization == null || authorization.length() == 0){
            return false;
        }
        return true;
    }

    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) {
        System.out.println("executeLogin");
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;

        String authorization = httpServletRequest.getHeader(JwtConstant.authorization_token);
        if(JwtUtils.isTokenExpiresAt(authorization)){
            //   token失效 提示前端需要自动跳转重新登陆
            return false;
        }
        if(!JwtUtils.verifyToken(authorization)){
            // token错误 提示前端需要自动跳转重新登陆
            return false;
        }
        JwtToken auth0JwtToken = new JwtToken(authorization);
        getSubject(httpServletRequest, httpServletResponse).login(auth0JwtToken);
        return true;
    }

    @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;
        }
        return super.preHandle(request, response);
    }

    private void invalidTokenRedirect(ServletResponse resp) {
        try {
            HttpServletResponse httpServletResponse = (HttpServletResponse) resp;
            httpServletResponse.sendRedirect("/shiro-redirect/invalidToken");
        } catch (IOException e) {
            System.out.println(e.toString());
        }
    }

    private void noAuthTokenRedirect(ServletResponse resp) {
        try {
            HttpServletResponse httpServletResponse = (HttpServletResponse) resp;
            httpServletResponse.sendRedirect("/shiro-redirect/noAuthToken");
        } catch (IOException e) {
            System.out.println(e.toString());
        }
    }


}

过滤器方式二ShiroFilter2.java

package boot.shiro.auth0.config;

import boot.shiro.auth0.domain.Response;
import boot.shiro.auth0.domain.ResponseCode;
import com.fasterxml.jackson.databind.ObjectMapper;
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 ShiroFilter2 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;
//        }

        HttpServletRequest req = (HttpServletRequest) request;
        String authorization = req.getHeader(JwtConstant.authorization_token);
        if(authorization == null || authorization.length() == 0){
            //  未携带token  不需要提示前端自动跳转重新登陆
            responseSkip(httpServletResponse, ResponseCode.noAuthHeaderTokenResponse("未携带token,请求无效"));
            return false;
        }
        if(JwtUtils.isTokenExpiresAt(authorization)){
            //   token失效 提示前端需要自动跳转重新登陆
            responseSkip(httpServletResponse, ResponseCode.invalidHeaderTokenSkipResponse());
            return false;
        }
        if(!JwtUtils.verifyToken(authorization)){
            // token错误 提示前端需要自动跳转重新登陆
            responseSkip(httpServletResponse, ResponseCode.invalidHeaderTokenSkipResponse());
            return false;
        }
        System.out.println("preHandle:");
        JwtToken auth0JwtToken = new JwtToken(authorization);
        getSubject(httpServletRequest, httpServletResponse).login(auth0JwtToken);
        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);
        }
    }


}

JwtConstant.java 可以定义在请求的header里的token具体用啥key方式

package boot.shiro.auth0.config;

public class JwtConstant {

    //  定义的请求头中使用的标记key,用来传递 token
    public static final String authorization_token = "token";

}

JwtDataMapper.java 模拟数据库的用户信息

package boot.shiro.auth0.config;

import boot.shiro.auth0.domain.UserEntity;

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

/**
 *  蚂蚁舞
 */
public class JwtDataMapper {

    public static final String shiro_admin = "shiro_admin";

    public static final String myw_admin = "myw_admin";

    public static final String app_admin = "app_admin";

    private static final UserEntity sysUsers_shiro_admin = new UserEntity(1, shiro_admin, "123", "shiro用户", 1, false);

    private static final UserEntity sysUsers_myw_admin = new UserEntity(2, myw_admin, "123", "蚂蚁舞用户", 2, false);

    private static final UserEntity sysUsers_app_admin = new UserEntity(3, app_admin, "123","app用户", 3, false);

    public static UserEntity 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;
    }


}

跨域支持BeanConfig.java

package boot.shiro.auth0.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);
	}


}

全局异常管理GlobalExceptionHandler.java

package boot.shiro.auth0.config;


import boot.shiro.auth0.domain.Response;
import boot.shiro.auth0.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("你输入的密码错误");
    }

}

SwaggerConfig.java

package boot.shiro.auth0.config;

import com.google.common.base.Predicates;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Parameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

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

/**
 *  蚂蚁舞
 */
@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Bean
    public Docket createRestApi(){
        String defaultToken = "";
        ParameterBuilder headerToken = new ParameterBuilder();
        List<Parameter> pars = new ArrayList<>();
        headerToken.name("token").description("token验证").modelRef(new ModelRef("string")).scalarExample(defaultToken).parameterType("header").required(false).build();
        pars.add(headerToken.build());
        return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select()
                .apis(RequestHandlerSelectors.any()).paths(PathSelectors.any())
                .paths(Predicates.not(PathSelectors.regex("/error.*")))
                .paths(PathSelectors.regex("/.*"))
                .build().globalOperationParameters(pars).apiInfo(apiInfo());
    }

    private ApiInfo apiInfo(){
        return new ApiInfoBuilder()
                .title("测试接口")
                .description("测试接口")
                .version("0.01")
                .build();
    }

    /**
     * http://localhost:8179/doc.html  地址和端口根据实际项目查看
     */


}

用户类UserEntity.java

package boot.shiro.auth0.domain;

public class UserEntity {

    private long id; // 主键ID

    private String username; // 登录用户名

    private String password; // 登录密码

    private String nickName; // 昵称

    private int roleId;

    private Boolean locked; // 账户是否被锁定

    public UserEntity() {

    }

    public UserEntity(long id, String username, String password, String nickName, int roleId, Boolean locked) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.nickName = nickName;
        this.roleId = roleId;
        this.locked = locked;
    }



    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getNickName() {
        return nickName;
    }

    public void setNickName(String nickName) {
        this.nickName = nickName;
    }

    public Boolean getLocked() {
        return locked;
    }

    public void setLocked(Boolean locked) {
        this.locked = locked;
    }

    public int getRoleId() {
        return roleId;
    }

    public void setRoleId(int roleId) {
        this.roleId = roleId;
    }

    @Override
    public String toString() {
        return "UserEntity{" +
                "id=" + id +
                ", username='" + username + ''' +
                ", password='" + password + ''' +
                ", nickName='" + nickName + ''' +
                ", locked=" + locked +
                '}';
    }
}

登录类UserValidate.java

package boot.shiro.auth0.domain;

public class UserValidate {

    String username;

    String password;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }



}

Response.java

package boot.shiro.auth0.domain;


import com.fasterxml.jackson.annotation.JsonInclude;

@JsonInclude(JsonInclude.Include.NON_NULL)
public class Response {

    private boolean state;

    private int code;

    private String msg;

    private Object data;

    private long timestamp;

    public Response() {}

    public Response(boolean state, int code, String msg) {
        this.state = state;
        this.code = code;
        this.msg = msg;
        this.timestamp = System.currentTimeMillis()/1000;
    }

    public Response(boolean state, int code, String msg, Object data) {
        this.state = state;
        this.code = code;
        this.msg = msg;
        this.data = data;
        this.timestamp = System.currentTimeMillis()/1000;
    }

    public boolean isState() {
        return state;
    }

    public void setState(boolean state) {
        this.state = state;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public long getTimestamp() {
        return timestamp;
    }

    public void setTimestamp(long timestamp) {
        this.timestamp = timestamp;
    }


    @Override
    public String toString() {
        return "InsResponse{" +
                "state=" + state +
                ", code=" + code +
                ", msg='" + msg + ''' +
                ", data=" + data +
                ", timestamp=" + timestamp +
                '}';
    }


}

ResponseCode.java

package boot.shiro.auth0.domain;



public class ResponseCode {

    private static final boolean STATE_TRUE = true;

    private static final boolean STATE_FALSE = false;


    private static final int CODE_200 = 200;     //操作资源成功

    private static final int CODE_201 = 201;     //资源为空


    private static final int CODE_100 = 100;     //操作资源失败

    private static final int CORE_101 = 101;     //缺少必要参数

    private static final int CORE_102 = 102;     //参数无效返回



    private static final int CORE_303 = 303;     //请求token无效或者token失效到期  提示前端后,前端用户可手动刷新重新登陆

    private static final int CORE_304 = 304;     //请求token无效或者token失效到期  提示前端后,前端自动跳转到登录页面

    private static final int CORE_305 = 305;     //未携带token认证信息,提示前端后,前端用户可手动刷新重新登陆

    private static final int CORE_306 = 306;     //未携带token认证信息,提示前端后,前端自动跳转到登录页面

    private static final int CORE_307 = 307;     //未登录  前端用户可手动跳转到登录页面

    private static final int CORE_308 = 308;     //未登录  前端自动跳转到登录页面

    private static final int CORE_400 = 400;    //全局异常
    private static final int CORE_600 = 600;    //您的权限不够

    private static final int CORE_601 = 601;    //未经授权不可访问

    private static final String MSG_ALL_SUCCESS = "操作资源成功";

    private static final String MSG_SELECT_SUCCESS = "获取资源成功";

    private static final String MSG_UPDATE_SUCCESS = "更新资源成功";

    private static final String MSG_INSERT_SUCCESS = "新增资源成功";

    private static final String MSG_REMOVE_SUCCESS = "移除资源成功";

    private static final String MSG_DELETE_SUCCESS = "删除资源成功";

    private static final String MSG_ALL_FAIL = "操作资源失败";

    private static final String MSG_SELECT_FAIL = "获取资源失败";

    private static final String MSG_UPDATE_FAIL = "更新资源失败";

    private static final String MSG_INSERT_FAIL = "新增资源失败";

    private static final String MSG_REMOVE_FAIL = "移除资源失败";

    private static final String MSG_DELETE_FAIL = "删除资源失败";

    private static final String MSG_RESOURCES_EMPTY = "资源为空";

    private static final String MSG_PARAMETER_LACK = "缺少必要参数";

    private static final String MSG_PARAMETER_INVALID = "参数无效";

    private static final String MSG_EXCEPTION_GLOBAL = "操作异常,请重新尝试";

    private static final String MSG_TOKEN_INVALID = "请求token无效或者token失效到期";

    private static final String MSG_TOKEN_INVALID_SKIP = "请求token无效或者token失效到期,自动跳转登录页";

    private static final String MSG_TOKEN_NO_AUTH = "未携带token认证信息,请重新登陆";

    private static final String MSG_TOKEN_NO_AUTH_SKIP = "未携带token认证信息,请重新登陆, 自动跳转到登录页";

    private static final String MSG_TOKEN_NO_LOGIN = "未登录,请登录";

    private static final String MSG_TOKEN_NO_LOGIN_SKIP = "未登录,请登录 自动跳转到登录页";

    private static final String MSG_TOKEN_NO_PERMISSION  = "未经授权的不可访问";

    private static final String MSG_TOKEN_NOT_PERMISSION = "您的权限不足";



    /**
     * 未登录,请登录
     */
    public static Response noLoginResponse(){
        return new Response(STATE_FALSE,CORE_307,MSG_TOKEN_NO_LOGIN);
    }

    public static Response noLoginResponse(String msg){
        return new Response(STATE_FALSE,CORE_307,msg);
    }

    public static Response noLoginSkipResponse(){
        return new Response(STATE_FALSE,CORE_308,MSG_TOKEN_NO_LOGIN_SKIP);
    }

    public static Response noLoginSkipResponse(String msg){
        return new Response(STATE_FALSE,CORE_308,msg);
    }


    //  未携带token认证信息,请重新登陆
    public static Response noAuthHeaderTokenResponse(){
        return new Response(STATE_FALSE,CORE_305,MSG_TOKEN_NO_AUTH);
    }

    public static Response noAuthHeaderTokenResponse(String msg){
        return new Response(STATE_FALSE,CORE_305,msg);
    }

    //  未携带token认证信息,提示前端后,前端自动跳转到登录页面
    public static Response noAuthHeaderTokenSkipResponse(){
        return new Response(STATE_FALSE,CORE_306,MSG_TOKEN_NO_AUTH_SKIP);
    }

    public static Response noAuthHeaderTokenSkipResponse(String msg){
        return new Response(STATE_FALSE,CORE_306,msg);
    }


    //  请求token无效或者token失效到期
    public static Response invalidHeaderTokenResponse(){
        return new Response(STATE_FALSE,CORE_303,MSG_TOKEN_INVALID);
    }

    public static Response invalidHeaderTokenResponse(String msg){
        return new Response(STATE_FALSE,CORE_303,msg);
    }

    //  请求token无效或者token失效到期  前端可根据此项code跳转到登录页
    public static Response invalidHeaderTokenSkipResponse(){
        return new Response(STATE_FALSE,CORE_304,MSG_TOKEN_INVALID_SKIP);
    }

    public static Response invalidHeaderTokenSkipResponse(String msg){
        return new Response(STATE_FALSE,CORE_304,msg);
    }


    //  全局异常返回
    public static Response exceptionResponse(){
        return new Response(STATE_FALSE,CORE_400,MSG_EXCEPTION_GLOBAL);
    }

    public static Response exceptionResponse(String msg){
        return new Response(STATE_FALSE,CORE_400,msg);
    }

    //  您的权限不足
    public static Response notPermissionResponse(){
        return new Response(STATE_FALSE,CORE_600,MSG_TOKEN_NOT_PERMISSION);
    }

    public static Response notPermissionResponse(String msg){
        return new Response(STATE_FALSE,CORE_600,msg);
    }

    //  未经授权的不可访问
    public static Response unauthorizedPermissionResponse(){
        return new Response(STATE_FALSE,CORE_601,MSG_TOKEN_NO_PERMISSION);
    }

    public static Response unauthorizedPermissionResponse(String msg){
        return new Response(STATE_FALSE,CORE_601,msg);
    }


    //  缺少必要参数返回
    public static Response lackParameterResponse(){
        return new Response(STATE_FALSE, CORE_101, MSG_PARAMETER_LACK);
    }

    public static Response lackParameterResponse(String msg){
        return new Response(STATE_FALSE, CORE_101, msg);
    }


    //  参数无效通用返回
    public static Response invalidParameterResponse(){
        return new Response(STATE_FALSE, CORE_102, MSG_PARAMETER_INVALID);
    }

    public static Response invalidParameterResponse(String msg){
        return new Response(STATE_FALSE, CORE_102, msg);
    }


    //  新增成功返回
    public static Response successInsertResponse(){
        return new Response(STATE_TRUE,CODE_200,MSG_INSERT_SUCCESS);
    }

    public static Response successInsertResponse(Object data){
        return new Response(STATE_TRUE,CODE_200,MSG_INSERT_SUCCESS,data);
    }


    //  更新成功返回
    public static Response successUpdateResponse(){
        return new Response(STATE_TRUE,CODE_200,MSG_UPDATE_SUCCESS);
    }


    //  查询成功返回
    public static Response successSelectResponse(){
        return new Response(STATE_TRUE,CODE_200,MSG_SELECT_SUCCESS);
    }

    public static Response successSelectResponse(Object data){
        return new Response(STATE_TRUE, CODE_200, MSG_SELECT_SUCCESS, data);
    }


    //  移除成功返回
    public static Response successRemoveResponse(){
        return new Response(STATE_TRUE,CODE_200,MSG_REMOVE_SUCCESS);
    }


    //  删除成功返回
    public static Response successDeleteResponse(){
        return new Response(STATE_TRUE,CODE_200,MSG_DELETE_SUCCESS);
    }


    //  通用success返回
    public static Response successResponse(){
        return new Response(STATE_TRUE,CODE_200,MSG_ALL_SUCCESS);
    }

    public static Response successResponse(String msg){
        return new Response(STATE_TRUE,CODE_200,msg);
    }

    public static Response successResponse(Object data){
        return new Response(STATE_TRUE,CODE_200,MSG_ALL_SUCCESS, data);
    }

    public static Response successResponse(String msg, Object data){
        return new Response(STATE_TRUE,CODE_200,msg, data);
    }

    //  查询结果为空时成功返回(没有获取到数据)
    public static Response successEmptyResponse(){
        return new Response(STATE_TRUE,CODE_201,MSG_RESOURCES_EMPTY);
    }

    public static Response successEmptyResponse(String msg){
        return new Response(STATE_TRUE,CODE_201,msg);
    }

    public static Response successEmptyResponse(Object data){
        return new Response(STATE_TRUE,CODE_201,MSG_RESOURCES_EMPTY,data);
    }


    //  新增失败返回
    public static Response failInsertResponse(){
        return new Response(STATE_FALSE,CODE_100,MSG_INSERT_FAIL);
    }

    public static Response failInsertResponse(String msg){
        return new Response(STATE_FALSE,CODE_100,msg);
    }


    //  更新失败返回
    public static Response failUpdateResponse(){
        return new Response(STATE_FALSE,CODE_100,MSG_UPDATE_FAIL);
    }

    public static Response failUpdateResponse(String msg){
        return new Response(STATE_FALSE,CODE_100,msg);
    }


    //  查询失败返回
    public static Response failSelectResponse(){
        return new Response(STATE_FALSE,CODE_100,MSG_SELECT_FAIL);
    }

    public static Response failSelectResponse(String msg){
        return new Response(STATE_FALSE,CODE_100,msg);
    }


    //  移除失败返回
    public static Response failRemoveResponse(){
        return new Response(STATE_FALSE,CODE_100,MSG_REMOVE_FAIL);
    }

    public static Response failRemoveResponse(String msg){
        return new Response(STATE_FALSE,CODE_100,msg);
    }


    //  删除失败返回
    public static Response failDeleteResponse(){
        return new Response(STATE_FALSE,CODE_100,MSG_DELETE_FAIL);
    }

    public static Response failDeleteResponse(String msg){
        return new Response(STATE_FALSE,CODE_100,msg);
    }


    //  通用fail返回
    public static Response failResponse(){
        return new Response(STATE_FALSE,CODE_100,MSG_ALL_FAIL);
    }

    public static Response failResponse(String msg){
        return new Response(STATE_FALSE,CODE_100,msg);
    }

    public static Response failResponse(String msg, Object data){
        return new Response(STATE_FALSE,CODE_100,msg,data);
    }


    //  自定义返回结果
    public static Response customizeResponse(boolean state, int code, String msg){
        return new Response(state,code,msg);
    }

    public static Response customizeResponse(boolean state, int code, String msg, Object data){
        return new Response(state, code, msg, data);
    }








}


BootShiroAnonController.java

package boot.shiro.auth0.controller;


import boot.shiro.auth0.domain.Response;
import boot.shiro.auth0.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 BootShiroAnonController {

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


}

BootShiroIndexController.java

package boot.shiro.auth0.controller;


import boot.shiro.auth0.domain.Response;
import boot.shiro.auth0.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 BootShiroIndexController {

    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();
    }

    @RequestMapping("/noAuthToken")
    @ResponseBody
    public Response noAuthToken() {
        log.warn("redirect noAuthToken");
        return ResponseCode.noAuthHeaderTokenSkipResponse();
    }

    @RequestMapping("/invalidToken")
    @ResponseBody
    public Response invalidHeaderToken() {
        log.warn("redirect invalidHeaderToken");
        return ResponseCode.invalidHeaderTokenSkipResponse();
    }



}

BootShiroLoginController.java

package boot.shiro.auth0.controller;

import boot.shiro.auth0.config.JwtDataMapper;
import boot.shiro.auth0.config.JwtToken;
import boot.shiro.auth0.config.JwtUtils;
import boot.shiro.auth0.domain.*;
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) {
		//	查询数据库的数据比对密码是否正确
		UserEntity userEntity = JwtDataMapper.getSysUsersByUserName(username);
		if(userEntity != null && userEntity.getPassword().equals(password)){
			// 生成jwt的token 交给shiro验证,实际上在这里可以直接返回登陆正确了,
			// 在过滤器里也可以执行的,在这里执行一次相当于登录的二次确认吧
			try {
				String token = JwtUtils.createToken(username);
				JwtToken jwtToken = new JwtToken(token);
				Subject subject = SecurityUtils.getSubject();
				subject.login(jwtToken);
				UserEntity sysUsers = (UserEntity) subject.getPrincipal();
				Map<String,Object> map = new HashMap<>();
				map.put("user", sysUsers);
				map.put("token", token);
				map.put("session", subject.getSession());
				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");
			}
		}
		return ResponseCode.failResponse("用户不存在");
	}

	@PostMapping(value="/auth")
	@ResponseBody
	public Response authPost(@RequestBody UserValidate userValidate) {
		System.out.println(userValidate.toString());
		//	查询数据库的数据比对密码是否正确
		UserEntity userEntity = JwtDataMapper.getSysUsersByUserName(userValidate.getUsername());
		if(userEntity != null && userEntity.getPassword().equals(userValidate.getPassword())){
			// 生成jwt的token 交给shiro验证,实际上在这里可以直接返回登陆正确了,
			// 在过滤器里也可以执行的,在这里执行一次相当于登录的二次确认吧
			try {
				String token = JwtUtils.createToken(userValidate.getUsername());
				JwtToken jwtToken = new JwtToken(token);
				Subject subject = SecurityUtils.getSubject();
				subject.login(jwtToken);
				UserEntity sysUsers = (UserEntity) subject.getPrincipal();
				Map<String,Object> map = new HashMap<>();
				map.put("user", sysUsers);
				map.put("token", token);
				map.put("session", subject.getSession());
				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");
			}
		}
		return ResponseCode.failResponse("用户不存在");
	}



}

BootShiroLogoutController.java

package boot.shiro.auth0.controller;

import boot.shiro.auth0.domain.Response;
import boot.shiro.auth0.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.isAuthenticated()){
            subject.logout();
            return ResponseCode.successResponse("登出成功");
        }
        return ResponseCode.failResponse("登出失败");
    }

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



}

BootShiroUserController.java

package boot.shiro.auth0.controller;


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

@Controller
@RequestMapping(value="/sysUser")
public class BootShiroUserController {

    @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("你已经成功访问到删除用户接口");
    }



}

启动类ShiroAppAuth0.java

package boot.shiro.auth0;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;


@SpringBootApplication
public class ShiroAppAuth0 {
    public static void main( String[] args )
    {
    	SpringApplication.run(ShiroAppAuth0.class, args);
        System.out.println( "Hello World!" );
    }
    
    
}

启动后的登录示意
前端vue+elemenui只是一个调用接口的demo,不完整,主要是测试用
axios request.js

import axios from 'axios'
import { getToken} from '@/utils/cookies'
import { Notification } from 'element-ui'


const service = axios.create({
    baseURL: "http://127.0.0.1:20401",
    timeout: 60000,
    headers: {'Content-Type': 'application/json;charset=UTF-8'}
})

// 统一请求拦截器
service.interceptors.request.use(
    config => {
        if("/shiro-login/auth" === config.url){
            return config
        }

        // 把token给后端
        config.headers['token'] = getToken()
        if(getToken()){
            config.headers['token'] = getToken()
        } else {
            Notification({title: '消息',message: 'token失效,请重新登陆',type: 'warning',offset: 40})
            return
        }
        return config
    },
    error => {
        // 请求出错
        console.log(error)
        return Promise.reject(error)
    }
)

// 统一响应拦截器
service.interceptors.response.use (
    response => {
        let res
        // IE9时response.data是undefined,因此需要使用response.request.responseText(Stringify后的字符串)
        if (response.data == undefined) {
            res = JSON.parse(response.request.responseText)
        } else {
            res = response.data
        }
        //console.log(res);

        //响应的逻辑判断
        if(res){
            return res
        }
        return Promise.reject(new Error("请求错误" || 'Error'))
    },
    error => {
        //响应出错
        console.log('err' + error)
        return Promise.reject(error)
    }
)

export default service

api接口

import request from '@/axios/request'

// 登录
export const login = (data) => {
    return request({
        url: '/shiro-login/auth',
        method: 'post',
        data
    })
}

// 登出
export const logout = () => {
    return request({
        url: '/shiro-logout/logout',
        method: 'post'
    })
}

// 匿名游客
export function anonHello() {
    return request({
        url: '/shiro-anon/hello',
        method: 'get'
    })
}

// 认证用户
export function authcHello() {
    return request({
        url: '/shiro-authc/hello',
        method: 'get'
    })
}

// 查询用户
export function userList() {
    return request({
        url: '/sysUser/list',
        method: 'get'
    })
}

// 新增用户
export function userInsert() {
    return request({
        url: '/sysUser/insert',
        method: 'get'
    })
}

// 更新用户
export function userUpdate() {
    return request({
        url: '/sysUser/update',
        method: 'get'
    })
}

// 删除用户
export function userDelete() {
    return request({
        url: '/sysUser/delete',
        method: 'get'
    })
}



import * as shiroVue from './modules/shiroVue'
// 默认全部导出
export default {
    shiroVue
}


// 导入所有接口
import api from './api'

const install = Vue => {
    if (install.installed){
        return;
    }
    install.installed = true;
    Object.defineProperties(Vue.prototype, {
        // 注意,此处挂载在 Vue 原型的 $api 对象上
        $api: {
            get() {
                return api
            }
        }
    })
}

export default install

router.js index.js


import Vue from 'vue'
import VueRouter from 'vue-router'
import {routers} from './router'

Vue.use(VueRouter)

const createRouter = () => new VueRouter({
    // mode: 'history',
    routes: routers
})

const router = createRouter();

const VueRouterPush = VueRouter.prototype.push
VueRouter.prototype.push = function push (to) {
  return VueRouterPush.call(this, to).catch(err => err)
}

// 挂载路由守卫
router.beforeEach((to, form, next) => {
    return next()
})

export default router

export const baseRouter = [
    {
        path: '/',
        name: 'app',
        redirect: '/home',
        meta: { title: '首页', icon:'el-icon-s-home'},
    },    
    {
        path: '/home',
        name: 'home',
        component: () => import('@/views/Home.vue')
    }
]


export const routers = [
    ...baseRouter
];

token缓存

/**
 *  token认证
 * 
 */
import Cookies from 'js-cookie'

const mywTokenKey = 'mywToken'

export function getToken() {
    return Cookies.get(mywTokenKey)
}
export function setToken(token) {
    return Cookies.set(mywTokenKey, token)
}
export function removeToken() {
    return Cookies.remove(mywTokenKey)
}

<template>

<div style="border-radius:4px;padding:4px;">
    <el-row style="padding-top:40px;">   
        <el-col :span="24">
            <div>SpringBoot+Shiro框架整合实现前后端分离的权限管理基础Demo</div>
        </el-col>             
        <el-col :span="24" style="margin-top: 20px;">
            <el-input placeholder="用户账号" style="width:200px;margin-right:8px;" v-model="username" clearable></el-input>
            <el-input placeholder="用户密码" style="width:200px;margin-right:8px;" v-model="password" clearable></el-input>
        </el-col>
        <el-col :span="24" style="margin-top: 20px;">
            <el-button type="info" @click="handleLogin()">登录系统</el-button>
            <div style="height:4px;">{{ resultLogin }}</div>            
        </el-col>
        <el-col :span="24" style="margin-top: 40px;">
            <el-button @click="handleLoGout()">登出系统</el-button>
            <div style="height:4px;">{{ resultLogout }}</div>
        </el-col>
        <el-col :span="24" style="margin-top: 20px;">
            <el-button @click="handleanonHello()">匿名游客</el-button>
            <div style="height:4px;">{{ resultAnonHello }}</div>
        </el-col>
        <el-col :span="24" style="margin-top: 20px;">
            <el-button type="info" @click="handleuserList()">查询用户</el-button>
            <div style="height:4px;">{{ resultuserList }}</div>
        </el-col>
        <el-col :span="24" style="margin-top: 20px;">
            <el-button type="warning" @click="handleuserInsert()">新增用户</el-button>  
            <div style="height:4px;">{{ resultuserInsert }}</div>
        </el-col>
        <el-col :span="24" style="margin-top: 20px;">
            <el-button @click="handleuserUpdate()" type="success">编辑用户</el-button>
            <div style="height:4px;">{{ resultuserUpdate }}</div>
        </el-col>
        <el-col :span="24" style="margin-top: 20px;">
            <el-button @click="handleuserDelete()">删除用户</el-button>
            <div style="height:4px;">{{ resultuserDelete }}</div>
        </el-col>
        
        <el-col :span="24" style="margin-top: 30px;">
            <el-button type="info" @click="handleauthcHello()">认证访问(特殊)</el-button>
            <div style="height:4px;">{{ resultAuthcHello }}</div>
        </el-col>        
    </el-row>    
</div>
</template>

<script>
import { login, logout, anonHello, authcHello, userList, userInsert, userUpdate, userDelete} from '@/api/modules/shiroVue'
import {setToken, removeToken } from '../utils/cookies'
export default {
    name: 'Home',
    data() {
        return {
            username: "shiro_admin",
            password: "123",
            resultLogin: "",
            resultLogout: "",
            resultAnonHello: "",
            resultAuthcHello: "",
            resultuserList: "",
            resultuserInsert: "",
            resultuserUpdate: "",
            resultuserDelete: ""
        }
    },
    created() {
    },
    methods: {
        init() {
        },
        handleLogin(){
            if(this.username && this.password){
                var data = {username: this.username, password: this.password}
                login(data).then((response) => {
                    console.log(response)
                    if(response.state){
                        setToken(response.data.token)
                        this.resultLogin = "msg:"+response.msg+" token:"+ response.data.token
                    } else {
                        this.resultLogin = "msg:"+ response.msg
                    }
                }).catch(response => {
                    console.log(response);
                });
            }
        },
        handleLoGout(){
            logout().then((response) => {
                console.log(response)
                removeToken()
                this.resultLogout = "msg:"+ response.msg
            }).catch(response => {
                console.log(response);
            });
        },   
        handleanonHello(){
            anonHello().then((response) => {
                console.log(response)
                this.resultAnonHello = "msg:"+ response.msg
            }).catch(response => {
                console.log(response);
            });
        },
        handleauthcHello(){
            authcHello().then((response) => {
                console.log(response)
                this.resultAuthcHello = "msg:"+ response.msg
            }).catch(response => {
                console.log(response);
            });
        },
        handleuserList(){
            userList().then((response) => {
                console.log(response)
                this.resultuserList = "msg:"+ response.msg
            }).catch(response => {
                console.log(response);
            });
        },
        handleuserInsert(){
            userInsert().then((response) => {
                console.log(response)
                this.resultuserInsert = "msg:"+ response.msg
            }).catch(response => {
                console.log(response);
            });
        },
        handleuserUpdate(){
            userUpdate().then((response) => {
                console.log(response)
                this.resultuserUpdate = "msg:"+ response.msg
            }).catch(response => {
                console.log(response);
            });
        },
        handleuserDelete(){
            userDelete().then((response) => {
                console.log(response)
                this.resultuserDelete = "msg:"+ response.msg
            }).catch(response => {
                console.log(response);
            });
        }                                                               
    },
    mounted() {
        this.$nextTick(function () {
            this.init()
        })
    },
    watch: {
    }

}
</script>

前后端登录以及访问接口测试
1.myw_admin 123 有user带注解的四个接口访问权限
myw_admin
2.app_admin 123 有user带注解的部分接口访问权限
app_admin

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。