记录一下使用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带注解的四个接口访问权限
2.app_admin 123 有user带注解的部分接口访问权限