记录一下使用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 {@Overridepublic boolean supports(AuthenticationToken token) {return token instanceof JwtToken;}// 授权@Overrideprotected 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;}// 认证@Overrideprotected 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 tokenprivate String token;public JwtToken(String token) {this.token = token;}@Overridepublic Object getPrincipal() {return this.token;}@Overridepublic 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信息,默认是falsejwtRealm.setAuthenticationCachingEnabled(true);//缓存AuthenticationInfo信息的缓存名称jwtRealm.setAuthenticationCacheName("authenticationCache");//启用授权缓存,即缓存AuthorizationInfo信息,默认是falsejwtRealm.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自带的sessionDefaultSubjectDAO 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;}@Beanpublic 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 {@Overrideprotected 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;}@Overrideprotected 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;}@Overrideprotected 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;}@Overrideprotected 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;}@Overrideprotected 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,用来传递 tokenpublic 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 {@Beanpublic 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)@ResponseBodypublic Response defaultExceptionHandler(HttpServletRequest request, Exception e) {log.error(request.getRequestURI()+"----"+e.toString());return ResponseCode.exceptionResponse(request.getRequestURI()+e.toString());}@ExceptionHandler(BindException.class)@ResponseBodypublic Response bindExceptionHandler(HttpServletRequest request, BindException e) {return ResponseCode.exceptionResponse(e.toString());}// 全局异常:请求header缺少HeaderToken@ExceptionHandler(ServletRequestBindingException.class)@ResponseBodypublic Response ServletRequestBindingExceptionHandler(HttpServletRequest request, ServletRequestBindingException e) {log.error(request.getRequestURI()+"----"+e.toString());return ResponseCode.noAuthHeaderTokenResponse();}// 全局异常:请求内容类型异常@ExceptionHandler(HttpMediaTypeNotSupportedException.class)@ResponseBodypublic Response HttpMediaTypeNotSupportedExceptionHandler(HttpServletRequest request, HttpMediaTypeNotSupportedException e) {log.error(request.getRequestURI()+"----"+e.toString());return ResponseCode.exceptionResponse(e.toString());}// 全局异常:请求方法异常@ExceptionHandler(HttpRequestMethodNotSupportedException.class)@ResponseBodypublic Response HttpRequestMethodNotSupportedExceptionHandler(HttpServletRequest request, HttpRequestMethodNotSupportedException e) {log.error(request.getRequestURI() +"----"+e.toString());return ResponseCode.exceptionResponse(e.toString());}// 全局异常:请求参数格式或者参数类型不正确异常@ExceptionHandler(HttpMessageNotReadableException.class)@ResponseBodypublic Response HttpMessageNotReadableExceptionHandler(HttpServletRequest request, HttpMessageNotReadableException e) {log.error(request.getRequestURI()+"----"+e.toString());return ResponseCode.exceptionResponse(e.toString());}// shiro 权限不可用@ExceptionHandler(InvalidPermissionStringException.class)@ResponseBodypublic Response InvalidPermissionStringException(HttpServletRequest request, IncorrectCredentialsException e) {log.error(request.getRequestURI()+"----"+e.toString());return ResponseCode.notPermissionResponse("你的权限不可用");}// shiro 未授权异常@ExceptionHandler(UnauthorizedException.class)@ResponseBodypublic Response UnauthorizedExceptionHandler(HttpServletRequest request, UnauthorizedException e) {log.error(request.getRequestURI()+"----"+e.toString());return ResponseCode.unauthorizedPermissionResponse("未授权,您的操作权限不够,可联系管理员获取操作权限");}// shiro 授权异常@ExceptionHandler(AuthorizationException.class)@ResponseBodypublic Response AuthorizationException(HttpServletRequest request, AuthorizationException e) {log.error(request.getRequestURI()+"----"+e.toString());return ResponseCode.failResponse( "授权用户不存在或已经过期,请重新登录");}// shiro 未经身份验证或身份验证异常@ExceptionHandler(UnauthenticatedException.class)@ResponseBodypublic Response UnauthenticatedException(HttpServletRequest request, UnauthenticatedException e) {log.error(request.getRequestURI()+"----"+e.toString());return ResponseCode.failResponse("未经身份验证,身份验证异常,请重新登录");}// shiro 账号锁定异常@ExceptionHandler(LockedAccountException.class)@ResponseBodypublic Response LockedAccountException(HttpServletRequest request, LockedAccountException e) {log.error(request.getRequestURI()+"----"+e.toString());return ResponseCode.failResponse("你的账号已锁定,请联系管理员解锁");}// shiro 未找到用户异常@ExceptionHandler(UnknownAccountException.class)@ResponseBodypublic Response UnknownAccountException(HttpServletRequest request, UnknownAccountException e) {log.error(request.getRequestURI()+"----"+e.toString());return ResponseCode.failResponse("你的账号不存在");}// shiro 登录用户密码校验异常@ExceptionHandler(IncorrectCredentialsException.class)@ResponseBodypublic 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 {@Beanpublic 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; // 主键IDprivate 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;}@Overridepublic 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;}@Overridepublic 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")@ResponseBodypublic Response index() {log.warn("redirect index");return ResponseCode.noLoginResponse();}@RequestMapping("/unauthorized")@ResponseBodypublic Response unauthorized() {log.warn("redirect unauthorized");return ResponseCode.unauthorizedPermissionResponse();}@RequestMapping("/noAuthToken")@ResponseBodypublic Response noAuthToken() {log.warn("redirect noAuthToken");return ResponseCode.noAuthHeaderTokenSkipResponse();}@RequestMapping("/invalidToken")@ResponseBodypublic 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")@ResponseBodypublic 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")@ResponseBodypublic 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")@ResponseBodypublic Response logoutGet() {Subject subject = SecurityUtils.getSubject();if(subject != null && subject.isAuthenticated()){subject.logout();return ResponseCode.successResponse("登出成功");}return ResponseCode.failResponse("登出失败");}@PostMapping(value="/logout")@ResponseBodypublic 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")@ResponseBodypublic Response userList() {return ResponseCode.successResponse("你已经成功访问到查询用户接口");}@RequiresPermissions("sys:user:add")@GetMapping(value="/insert")@ResponseBodypublic Response userAdd() {return ResponseCode.successResponse("你已经成功访问到新增用户接口");}@RequiresPermissions("sys:user:update")@GetMapping(value="/update")@ResponseBodypublic Response userUpdate() {return ResponseCode.successResponse("你已经成功访问到更新用户接口");}@RequiresPermissions("sys:user:delete")@GetMapping(value="/delete")@ResponseBodypublic 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带注解的四个接口访问权限
2.app_admin 123 有user带注解的部分接口访问权限