SpringBoot+Shiro+Jedis+JWT+基于url的权限拦截系统
设计思路
- shiro配置放行uri,比如登录登出等游客身份也能调用的接口等资源,登陆成功的用户服务器会返回特有的jwt(作为令牌)给前端交由前台保存,可保存到localStorage或sessionStorage,之后该用户的请求的header都需带上jwt
- 因为是基于url的拦截,新建拦截器继承BasicHttpAuthenticationFilter,重写preHadle(),isAccessAllowed().onAccessDenied()方法,主要功能:捕获请求,获取请求头的token进行身份验证,获取请求uri进行权限认证,认证通过才能调用controller接口
- 自定义realm类重写doGetAuthenticationInfo(),doGetAuthorizationInfo()方法,自定义身份认证及权限认证方式,给上面的拦截器所调用
- 生成的jwt会保存到redis交由redis维护,以用户名为key,过期时间30分钟,每次请求经过拦截器时候会刷新过期时间,若reidis获取不到jwt或jwt与请求头的jwt不一致则认证失败返回登陆页重新登陆
上菜
导包(展示部分)
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>3.0.7.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-support -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-support</artifactId>
<version>2.3.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.7.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter -->
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.55</version>
</dependency>
<!-- JWT:Json Web Token 配置 用于请求身份验证 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jjwt.version}</version>
</dependency>
<!-- Shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>
<!-- Redis-Jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>${jedis.version}</version>
</dependency>
</dependencies>
配置Jedis
package com.rq.authority.config.redis;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
* Jedis配置,项目启动注入JedisPool
*/
@Configuration
@EnableAutoConfiguration
public class JedisConfig {
/**
* LOGGER
*/
private static final Logger logger = LoggerFactory.getLogger(JedisConfig.class);
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.password}")
private String password;
@Value("${spring.redis.timeout}")
private int timeout;
@Value("200")
private int maxActive;
@Value("-1")
private int maxWait;
@Value("8")
private int maxIdle;
@Value("0")
private int minIdle;
@Bean
public JedisPool redisPoolFactory(){
try {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxIdle(maxIdle);
jedisPoolConfig.setMaxWaitMillis(maxWait);
jedisPoolConfig.setMaxTotal(maxActive);
jedisPoolConfig.setMinIdle(minIdle);
JedisPool jedisPool = new JedisPool(jedisPoolConfig, host, port, timeout, password);
logger.info("初始化Redis连接池JedisPool成功!地址: " + host + ":" + port);
return jedisPool;
} catch (Exception e) {
logger.error("初始化Redis连接池JedisPool异常:" + e.getMessage());
}
return null;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getTimeout() {
return timeout;
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
public int getMaxActive() {
return maxActive;
}
public void setMaxActive(int maxActive) {
this.maxActive = maxActive;
}
public int getMaxWait() {
return maxWait;
}
public void setMaxWait(int maxWait) {
this.maxWait = maxWait;
}
public int getMaxIdle() {
return maxIdle;
}
public void setMaxIdle(int maxIdle) {
this.maxIdle = maxIdle;
}
public int getMinIdle() {
return minIdle;
}
public void setMinIdle(int minIdle) {
this.minIdle = minIdle;
}
}
shiro 配置
package com.rq.authority.config.shiro;
import com.rq.authority.config.shiro.cache.CustomCacheManager;
import com.rq.authority.config.shiro.realm.StatelessRealm;
import com.rq.authority.filter.StatelessAuthcFilter;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.DefaultSessionManager;
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.apache.shiro.web.mgt.DefaultWebSubjectFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
private final static Logger logger = LoggerFactory.getLogger(ShiroConfig.class);
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
logger.info("设置拦截器");
Map<String, Filter> filters = new LinkedHashMap<String, Filter>();
//无状态过滤
filters.put("statelessAuthc", statelessAuthc());
shiroFilterFactoryBean.setFilters(filters);
logger.info("配置访问权限,拦截策略以键值对存入map");
Map<String, String> filterChainDefinitions = new LinkedHashMap<>();//必须LinkedHashMap
filterChainDefinitions.put("/**", "statelessAuthc");//拦截自定义设置
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitions);
return shiroFilterFactoryBean;
}
@Bean(name = "securityManager")
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置不创建session
securityManager.setSubjectFactory(subjectFactory());
logger.info("设置sessionManager禁用掉会话调度器");
securityManager.setSessionManager(sessionManager());
logger.info("注入缓存管理器");
securityManager.setCacheManager(new CustomCacheManager());//这个如果执行多次,也是同样的一个对象;
//无状态需要设置不创建session,禁用使用Sessions 作为存储策略的实现,但它没有完全地禁用Sessions,所以需要配合context.setSessionCreationEnabled(false);
((DefaultSessionStorageEvaluator)((DefaultSubjectDAO)securityManager.getSubjectDAO()).getSessionStorageEvaluator()).setSessionStorageEnabled(false);
logger.info("配置realm");
securityManager.setRealm(statelessRealm());
return securityManager;
}
/**
* subject工厂管理器.
* 用于禁用session
* @return
*/
@Bean
public DefaultWebSubjectFactory subjectFactory(){
StatelessDefaultSubjectFactory subjectFactory = new StatelessDefaultSubjectFactory();
return subjectFactory;
}
/**
* session管理器
* sessionManager通过sessionValidationSchedulerEnabled禁用掉会话调度器,
* 因为我们禁用掉了会话,所以没必要再定期过期会话了。
* @return
*/
@Bean
public DefaultSessionManager sessionManager(){
DefaultSessionManager sessionManager = new DefaultSessionManager();
sessionManager.setSessionValidationSchedulerEnabled(false);
return sessionManager;
}
/**
* 自定义realm实现
* @return
*/
@Bean
public StatelessRealm statelessRealm(){
StatelessRealm statelessRealm = new StatelessRealm();
statelessRealm.setCachingEnabled(false);
return statelessRealm;
}
/**
* 无状态权限验证
* @return
*/
@Bean
public StatelessAuthcFilter statelessAuthc(){
StatelessAuthcFilter statelessAuthc = new StatelessAuthcFilter();
return statelessAuthc;
}
/**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
}
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
LifecycleBeanPostProcessor lifecycleBeanPostProcessor = new LifecycleBeanPostProcessor();
return lifecycleBeanPostProcessor;
}
/**
* 必须加否则shiro注解无法使用
* @return
*/
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
}
因为不需要session,设置session不创建(自定义工厂类继承DefaultWebSubjectFactory)
package com.rq.authority.config.shiro;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.SubjectContext;
import org.apache.shiro.web.mgt.DefaultWebSubjectFactory;
public class StatelessDefaultSubjectFactory extends DefaultWebSubjectFactory {
@Override
public Subject createSubject(SubjectContext context) {
//不创建session
context.setSessionCreationEnabled(false);
return super.createSubject(context);
}
}
shiro缓存方面的配置
package com.rq.authority.config.shiro.cache;
import com.rq.authority.common.constant.RedisConstant;
import com.rq.authority.util.JedisUtil;
import com.rq.authority.util.SerializableUtil;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import java.util.*;
/**
* 重写Shiro的Cache保存读取
*/
public class CustomCache<K,V> implements Cache<K,V> {
/**
* 缓存的key名称获取为shiro:cache:principalIdFieldName
* @param key
* @return java.lang.String
*/
private String getKey(Object key){
return RedisConstant.PREFIX_SHIRO_CACHE + key.toString();
}
/**
* 获取缓存
*/
@Override
public Object get(Object key) throws CacheException {
if(!JedisUtil.exists(this.getKey(key))){
return null;
}
return JedisUtil.getObject(this.getKey(key));
}
/**
* 保存缓存
*/
@Override
public Object put(Object key, Object value) throws CacheException {
// 设置Redis的Shiro缓存
return JedisUtil.setObject(this.getKey(key), value, RedisConstant.SHIRO_CACHE_EXPIRETIME);
}
/**
* 移除缓存
*/
@Override
public Object remove(Object key) throws CacheException {
if(!JedisUtil.exists(this.getKey(key))){
return null;
}
JedisUtil.existAndDelKey(this.getKey(key));
return null;
}
/**
* 清空所有缓存
*/
@Override
public void clear() throws CacheException {
JedisUtil.getJedis().flushDB();
}
/**
* 缓存的个数
*/
@Override
public int size() {
Long size = JedisUtil.getJedis().dbSize();
return size.intValue();
}
/**
* 获取所有的key
*/
@Override
public Set keys() {
Set<byte[]> keys = JedisUtil.getJedis().keys(new String("*").getBytes());
Set<Object> set = new HashSet<Object>();
for (byte[] bs : keys) {
set.add(SerializableUtil.unserializable(bs));
}
return set;
}
/**
* 获取所有的value
*/
@Override
public Collection values() {
Set keys = this.keys();
List<Object> values = new ArrayList<Object>();
for (Object key : keys) {
values.add(JedisUtil.getObject(this.getKey(key)));
}
return values;
}
}
缓存管理器实例化自定义缓存类
package com.rq.authority.config.shiro.cache;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
/**
* 重写Shiro缓存管理器
*/
public class CustomCacheManager implements CacheManager {
@Override
public <K, V> Cache<K, V> getCache(String s) throws CacheException {
return new CustomCache<K,V>();
}
}
——压轴招牌菜——
噔噔~~~自定义拦截器StatelessAuthcFilter,基本整个后台系统的权限控制交由该类处理,具体怎么实现呢,挺简单的,好吃的一口塞,好喝的一口闷
package com.rq.authority.filter;
import com.rq.authority.common.handler.UnFindTokenException;
import com.rq.authority.config.shiro.bo.JwtToken;
import com.rq.authority.util.JwtTokenUtil;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.naming.AuthenticationException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class StatelessAuthcFilter extends BasicHttpAuthenticationFilter {
private static final Logger logger = LoggerFactory.getLogger(StatelessAuthcFilter.class);
//需要过滤拦截的请求地址,多个地址用","分割
@Value("${system.config.filterExclude}")
private String excludeUrl;
/**
* 默认需要放行的请求地址
*/
private String[] defalutExcludeUrl = new String[] {
"/websocket","/logout","/login","/authenticate","/formLogin",".jpg",".png",".gif",".css",".js",".jpeg"
};
/**
* CROS复杂请求时会先发送一个OPTIONS请求,来测试服务器是否支持本次请求,这个请求时不带数据的,请求成功后才会发送真实的请求。
* 所以第一次发送请求要确认服务器支不支持接收这个header。第二次才会传数据,所以我们要做的就是把所有的OPTIONS请求统统放行
* @param request
* @param response
* @return
* @throws Exception
*/
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpRequest = (HttpServletRequest)request;
HttpServletResponse httpResponse = (HttpServletResponse)response;
if (httpRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
httpResponse.setHeader("Access-control-Allow-Origin", "*");
httpResponse.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
httpResponse.setHeader("Access-Control-Allow-Headers", httpRequest.getHeader("Access-Control-Request-Headers"));
httpResponse.setStatus(HttpStatus.OK.value());
return false;
}
return super.preHandle(request, response);
}
/**
* 先执行:isAccessAllowed 再执行onAccessDenied
*
* isAccessAllowed:表示是否允许访问;mappedValue就是[urls]配置中拦截器参数部分,
* 如果允许访问返回true,否则false;
*
* 如果返回true的话,就直接返回交给下一个filter进行处理。
* 如果返回false的话,回往下执行onAccessDenied
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue){
logger.info("处理需过滤的请求");
String u = ((HttpServletRequest)request).getRequestURI();
for (String e : defalutExcludeUrl) {
if (u.endsWith(e)) {
return true;
}
}
if(StringUtils.isNotBlank(excludeUrl)){
String[] customExcludes = excludeUrl.split(",");
for (String e : customExcludes) {
if(e.contains("*")){
e = e.replace("*","");
if(u.startsWith(e)){
return true;
}
}
if (u.endsWith(e)) {
return true;
}
}
}
return false;
}
/**
* onAccessDenied:表示当访问拒绝时是否已经处理了;如果返回true表示需要继续处理;
* 如果返回false表示该拦截器实例已经处理了,将直接返回即可。
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
logger.info("获取token进行登陆验证");
String accessToken = this.getAuthzHeader(request);
if (StringUtils.isBlank(accessToken)) {
handleError(request,response, new UnFindTokenException(AUTHORIZATION_HEADER));
return false;
} else {
logger.info("验证accessToken");
try {
JwtToken token = JwtTokenUtil.validateAndGetToken(accessToken);
if (token != null) {
logger.info("委托realm类身份认证");
this.getSubject(request, response).login(token);
}
} catch (Exception e) {
Throwable cause = e.getCause();
logger.error("拦截器onAcessDenied()验证accessToken失败:{}",e);
handleError(request,response,new AuthenticationException(e.getMessage()));
return false;
}
}
String u = ((HttpServletRequest)request).getRequestURI();
logger.info("请求api:"+u);
Subject subject = SecurityUtils.getSubject();
logger.info("委托realm类授权认证");
if (subject.isPermitted(u)) {
return true;
}else {
handleError(request,response,new UnauthorizedException(u));
return false;
}
}
private void handleError(ServletRequest request, ServletResponse response, Exception e) throws Exception {
request.setAttribute("exception",e);
request.getRequestDispatcher("/exception/handle").forward(request,response);
}
}
上述未放行的uri会交由onAccessDenied()方法处理,主要业务逻辑:先获取请求头header里的authorization字段的token即jwt,验证后调用realm的doGetAuthenticationInfo()方法认证用户,再获取请求uri调用doGetAuthorizationInfo()方法验证是否合法请求,若是则通过直接调用controller类的方法,不是则返回异常信息,异常信息统一转发到异常处理controller类由全局异常处理切面类捕获处理。
——辅菜——
自定义realm类
package com.rq.authority.config.shiro.realm;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.rq.authority.common.constant.RedisConstant;
import com.rq.authority.config.shiro.bo.JwtToken;
import com.rq.authority.entity.Resource;
import com.rq.authority.entity.User;
import com.rq.authority.service.ResourceService;
import com.rq.authority.service.UserService;
import com.rq.authority.util.JedisUtil;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
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.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import java.util.List;
public class StatelessRealm extends AuthorizingRealm {
@Autowired
@Lazy
private ResourceService resourceService;
@Autowired
@Lazy
private UserService userService;
@Override
public boolean supports(AuthenticationToken token) {
/**
* 仅支持jwtToken 类型的Token,
* 那么如果在StatelessAuthcFilter类中返回的是UsernamePasswordToken,那么将会报如下错误信息:
* Please ensure that the appropriate Realm implementation is configured correctly or
* that the realm accepts AuthenticationTokens of this type.StatelessAuthcFilter.isAccessAllowed()
*/
return token instanceof JwtToken;
}
/**
* 认证方法
* @param auth
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
String account = (String) auth.getPrincipal();
String accessToken = (String) auth.getCredentials();
// 帐号为空
if (StringUtils.isBlank(account)) {
throw new AuthenticationException("Token中帐号为空(The account in Token is empty.)");
}
// 查询用户是否存在
User user = userService.selectOne(new EntityWrapper<User>().eq("username",account));
if (user == null) {
throw new AuthenticationException("该帐号不存在(The account does not exist.):" + account);
}
//校验accessToken
String refreshToken =(String) JedisUtil.getObject(RedisConstant.PREFIX_SHIRO_REFRESH_TOKEN + account);
if (StringUtils.isNotBlank(refreshToken) && accessToken.equals(refreshToken)) {
//刷新refreshToken
JedisUtil.expire(RedisConstant.PREFIX_SHIRO_REFRESH_TOKEN + account,RedisConstant.REFRESH_TOKEN_EXPIRETIME);
return new SimpleAuthenticationInfo(account, accessToken, getName());
}
throw new AuthenticationException("身份认证失败,请重新登陆!");
}
/**
* 授权,只有当需要检测用户权限的时候才会调用此方法,例如checkRole,checkPermission之类的
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
String username = (String) principals.getPrimaryPrincipal();
String userId = userService.selectOne(new EntityWrapper<User>().eq("username",username)).getId();
// 根据用户角色与所属组织获取资源交集
List<Resource> resources = resourceService.selectResourcesByUserId(userId);
if (!resources.isEmpty()) {
for (Resource resource : resources) {
simpleAuthorizationInfo.addStringPermission(resource.getUrl());
}
}
return simpleAuthorizationInfo;
}
}
异常处理类(controller)
package com.rq.authority.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@RestController
@RequestMapping("/exception")
public class ExceptionController {
@RequestMapping("/handle")
public void exceptionHandler(HttpServletRequest request, HttpServletResponse response) throws Throwable {
Exception e = (Exception) request.getAttribute("exception");
throw e;
}
}
全局捕获异常切面类
package com.rq.authority.common.handler;
import com.rq.authority.common.enums.ResultCode;
import com.rq.authority.common.vo.R;
import com.rq.authority.common.vo.ResponseBuilder;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.UnsupportedJwtException;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.authz.UnauthorizedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.naming.AuthenticationException;
import javax.servlet.http.HttpServletRequest;
/**
* title: 全局异常处理切面
* Description: 利用 @ControllerAdvice + @ExceptionHandler组合处理Controller层RuntimeException异常
*/
@RestControllerAdvice
public class ExceptionAspect {
private static final Logger logger = LoggerFactory.getLogger(ExceptionAspect.class);
/**
* 400 - Bad Request
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(HttpMessageNotReadableException.class)
public R<String> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
logger.error("JSON 格式错误:{}", e);
return ResponseBuilder.error(ResultCode.PARAM_ERROR,"JSON 格式错误");
}
/**
* 400 - Bad Request
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public R<String> handleValidationException(MethodArgumentNotValidException e) {
logger.error("参数异常:{}", e);
return ResponseBuilder.error(ResultCode.PARAM_ERROR,"参数异常");
}
/**
* 401 - 找不到JWT异常
*/
@ResponseStatus(HttpStatus.UNAUTHORIZED)
@ExceptionHandler(UnFindTokenException.class)
public R<String> handleUnfindTokenException(UnFindTokenException e) {
logger.error("找不到token异常:{}",e);
return ResponseBuilder.error(ResultCode.EXCEPTION,"找不到token:" + e.getMessage());
}
/**
* 401 - 验签异常
*/
@ResponseStatus(HttpStatus.UNAUTHORIZED)
@ExceptionHandler(SignatureException.class)
public R<String> handleSignatureException(SignatureException e) {
logger.error("token验签失败:{}", e);
return ResponseBuilder.error(ResultCode.EXCEPTION,"token验签失败:" + e.getMessage());
}
/**
* 401 - token过期异常
* @param e
* @return
*/
@ResponseStatus(HttpStatus.UNAUTHORIZED)
@ExceptionHandler(ExpiredJwtException.class)
public R<String> handleExpiredJwtException(ExpiredJwtException e) {
logger.error("token过期异常:{}",e);
return ResponseBuilder.error(ResultCode.EXCEPTION,"token过期:" + e.getMessage());
}
/**
* 401 - token解析异常
* @param e
* @return
*/
@ResponseStatus(HttpStatus.UNAUTHORIZED)
@ExceptionHandler({UnsupportedJwtException.class, MalformedJwtException.class})
public R<String> handleUnsupportedJwtException(UnsupportedJwtException e) {
logger.error("token解析异常:{}",e);
return ResponseBuilder.error(ResultCode.EXCEPTION,"token格式不正确,解析失败:" + e.getMessage());
}
/**
* 403 - shiro 认证异常
* @param
* @return
*/
@ResponseStatus(HttpStatus.FORBIDDEN)
@ExceptionHandler(UnauthenticatedException.class)
public R<String> handleUnauthenticatedException(UnauthorizedException e) {
logger.error("未经过身份认证:{}", e);
return ResponseBuilder.error(ResultCode.EXCEPTION,"未经身份认证,无权访问:" + e.getMessage());
}
/**
* 403 - shiro 授权异常
* @param
* @return
*/
@ResponseStatus(HttpStatus.FORBIDDEN)
@ExceptionHandler(UnauthorizedException.class)
public R<String> handleUnauthorizedException(UnauthorizedException e) {
logger.error("不在权限允许范围内:{}", e);
return ResponseBuilder.error(ResultCode.EXCEPTION,"超出权限范围,无权访问:"+e.getMessage());
}
/**
* 415 - Unsupported Media Type。HttpMediaTypeNotSupportedException
* 是ServletException的子类,需要Servlet API支持
*/
@ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
@ExceptionHandler(HttpMediaTypeNotSupportedException.class)
public R<String> handleHttpMediaTypeNotSupportedException(Exception e) {
logger.error("不支持的媒体类型:{}", e);
return ResponseBuilder.error(ResultCode.EXCEPTION,"不支持的媒体类型:"+e.getMessage());
}
/**
* 500 - shiro身份认证异常
*/
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(AuthenticationException.class)
public R<String> handleAuthenticationException(AuthenticationException e) {
logger.error("Shiro身份认证异常:{}", e);
return ResponseBuilder.error(ResultCode.EXCEPTION,"身份认证失败");
}
/**
* 500 - shiro授权处理异常
*/
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(AuthorizationException.class)
public R<String> handleAuthorizationException(AuthorizationException e) {
logger.error("Shiro授权处理异常:{}", e);
return ResponseBuilder.error(ResultCode.EXCEPTION,"授权处理失败");
}
@ExceptionHandler(value = BusinessException.class)
public R handleBusinessException(BusinessException e, HttpServletRequest request) {
logger.error("请求[{}]业务异常", request.getRequestURI(), e);
return R
.builder()
.code(e.getExceptionCode())
.message(e.getMessage())
.build();
}
@ExceptionHandler
public R handleSystemException(Exception e, HttpServletRequest request) {
logger.error("请求[{}]系统异常", request.getRequestURI(), e);
return ResponseBuilder.exception();
}
}
没吃饱?详情源码下载:https://download.****.net/download/qq_41981107/10937254