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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/17246.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

在Linux上搭建gitlab以及自动化编译部署的完整流程

一、安装gitlab 首先下载gitlab的安装包&#xff0c;地址如下&#xff1a; https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/ubuntu/pool/bionic/main/g/gitlab-ce/ 然后安装下载的包即可&#xff0c;一般还需要安装openssh-server等依赖包&#xff0c;在安装gitlab包之前可以…

睡眠经济2.0时代来了,老巨头们有护城河吗?

在第23个世界睡眠日&#xff0c;中国睡眠研究会等机构发布了《中国睡眠研究报告2023》&#xff0c;近半数人每晚平均睡眠时长不足8小时&#xff0c;“失眠”已成为了当代人的生活常态。 越是睡不好&#xff0c;越要想办法。年轻人纷纷求助于好的寝具、好的助眠产品乃至保健品&…

【Halcon】找到设备上的 标识牌

如图&#xff0c;找到设备上的 标识牌 。 标识牌最明显的特征是比其他区域亮&#xff0c; 二值化选择出亮区域&#xff0c;再通过面积选择出目标区域。 先显示图片 *获取图片的大小 get_image_size(Image,Width,Height)*关闭窗口 dev_close_window()*打开窗口 dev_open_win…

java错题总结(19-21页)

链接&#xff1a;关于Java中的ClassLoader下面的哪些描述是错误的_用友笔试题_牛客网 来源&#xff1a;牛客网 B&#xff1a;先讲一下双亲委派机制&#xff0c;简单来说&#xff0c;就是加载一个类的时候&#xff0c;会往上找他的父类加载器&#xff0c;父类加载器找它的父类加…

Centos系统安装RabbitMQ消息中间件

记录一下在centos7.x下面安装RabbitMQ消息中间件 RabbitMQ是一个开源而且遵循 AMQP协议实现的基于 Erlang语言编写&#xff0c;因此安装RabbitMQ之前是需要部署安装Erlang环境的 先安装Erlang https://packagecloud.io/rabbitmq/ 点进去可以看到 因为使用的centos是7.x版本的…

架构设计-数据库篇

大家好&#xff0c;我是易安&#xff01; 之前我们讲过架构设计的一些原则&#xff0c;和架构设计的方法论&#xff0c;今天我们谈谈高性能数据库集群的设计与应用。 读写分离原理 读写分离的基本原理是将数据库读写操作分散到不同的节点上&#xff0c;下面是其基本架构图。 读…

【Python系列】一个简单的抽奖小程序

序言 很开心你能在万千博文中打开这一篇&#xff0c;希望能给你带来一定的帮助&#xff01;&#x1f44d;&#x1f3fb; 如果有什么问题&#xff0c;都可以添加下方我的联系方式&#xff0c;联系我噢~&#x1f601; ⭐️⭐️⭐️⭐️⭐️沟通交流&#xff0c;一起成为技术达人&…

电视机顶盒哪个牌子好?数码小编盘点电视机顶盒排行榜

电视机顶盒哪个牌子好&#xff1f;这是困扰新手们的一大难题&#xff0c;部分产品被爆出虚标高配、偷工减料&#xff0c;面对众多的机顶盒品牌和型号&#xff0c;怎么选择才好&#xff1f;小编以销量和用户评价为标准&#xff0c;盘点了电视机顶盒排行榜&#xff0c;跟着我一起…

【Linux】进程学习(1)---理解进程概念

文章目录 冯诺依曼体系结构理解冯诺依曼体系结构 操作系统概念与定位概念计算机管理模型计算机的软硬件体系结构图系统调用和库函数概念 进程基本概念描述进程--PCBtask_struct内容分类组织进程 冯诺依曼体系结构 数学家冯诺依曼提出了计算机制造的三个基本原则&#xff0c;即采…

代码随想录算法训练营第四十八天| 198.打家劫舍、213.打家劫舍II、337.打家劫舍III

文章目录 198.打家劫舍213.打家劫舍II337.打家劫舍III 198.打家劫舍 题目链接&#xff1a;代码随想录 解题思路&#xff1a; 1.dp[i]&#xff1a;考虑下标i&#xff08;包括i&#xff09;以内的房屋&#xff0c;最多可以偷窃的金额为dp[i] 只是考虑&#xff0c;不一定偷 2.递推…

GPT-4等大语言模型对教育的未来意味着什么?

‍ ‍ shadow Mixlab这些年举办了非常多的活动和workshop&#xff0c;都带有很强的教育属性。今天我抽空学习了可汗学院的《AI-for-Education》课程&#xff0c;非常有启发。我记录了精华内容&#xff0c;分享给大家。 课程地址&#xff1a; www.khanacademy.org/college-caree…

设计模式——观察者模式

导航&#xff1a; 【黑马Java笔记踩坑汇总】JavaSEJavaWebSSMSpringBoot瑞吉外卖SpringCloud黑马旅游谷粒商城学成在线设计模式牛客面试题 目录 观察者模式 1、天气预报需求 2、天气预报需求方案之普通方案 3、观察者模式介绍 4、观察者模式优化天气预报案例 5、JDK 的O…

销售数据分析怎么做?这篇文章说清楚了

如何分析销售数据&#xff1f;分析销售数据有哪些指标&#xff1f;销售数据分析有什么作用&#xff1f; 销售数据是不是得通过数据分析软件啊&#xff1f; 本文将为您解答疑惑—— 一、分析销售数据的指标 从两个层面上来讲&#xff0c;一个是对销售情况的整体把控&#xf…

红黑树理论详解与Java实现

文章目录 基本定义五大性质红黑树和2-3-4树的关系红黑树和2-3-4树各结点对应关系添加结点到红黑树注意事项添加的所有情况 添加导致不平衡叔父节点不是红色节点&#xff08;祖父节点为红色&#xff09;添加不平衡LL/RR添加不平衡LR/RL 叔父节点是红色节点&#xff08;祖父节点为…

破解马赛克有多「容易」?

刷短视频时&#xff0c;估计大家都看过下面这类视频&#xff0c;各家营销号争相曝光「一分钟解码苹果笔刷背后内容」的秘密。换汤不换药&#xff0c;自媒体们戏称其为「破解马赛克」&#xff0c;殊不知让多少不明真相的用户建立起了错误的认知&#xff0c;也让苹果笔刷第 10086…

【面试】嵌入式C语言题目整理

【面试】嵌入式C语言题目整理 描述内存四区。 内存四区分为&#xff1a;代码区、静态区、堆区、栈区 代码区就是用来存放代码的。 静态区用来存放全局变量、静态变量、常量&#xff08;字符串常量、const修饰的全局变量&#xff09;。 堆区中的内存是由程序员自己申请和释放的&…

九、MyBatis动态SQL

文章目录 九、动态SQL9.1 if9.2 where9.3 trim9.4 choose、when、otherwise9.5 foreach9.6 SQL片段 本人其他相关文章链接 九、动态SQL 9.1 if 总结&#xff1a;根据标签中test属性所对应的表达式决定标签中的内容是否需要拼接到SQL中。 User getUserByParamsWithIf(User user…

Packet Tracer - 在思科路由器上配置 AAA 认证

Packet Tracer - 在思科路由器上配置 AAA 认证 拓扑图 地址分配表 设备 接口 IP 地址 子网掩码 默认网关 交换机端口 R1 G0/1 192.168.1.1 255.255.255.0 不适用 S1 F0/1 S0/0/0 (DCE) 10.1.1.2 255.255.255.252 不适用 不适用 R2 G0/0 192.168.2.1 255.2…

(四)Kubernetes - 手动部署(二进制方式安装)

Kubernetes - 手动部署 [ 3 ] 1 部署work node1.1 创建工作目录并拷贝二进制文件1.2 部署kubelet1.2.1 创建配置文件1.2.2 配置文件1.2.3 生成kubelet初次加入集群引导kubeconfig文件1.2.4 systemd管理kubelet1.2.5 启动并设置开机启动1.2.6 允许kubelet证书申请并加入集群 1.3…

JAVA-异常

文章目录 1.异常的体系1.3异常的分类 2.异常的处理2.2异常的抛出throw2.3异常的捕获2.3.1异常声明throws2.3.2 try-catch捕获并处理2.3.3 finally 2.4 异常的处理流程 3.自定义异常类 1.异常的体系 Throwable&#xff1a;是异常体系的顶层类&#xff0c;其派生出两个重要的子类…