目录
一、简介:
JWT优点:
JWT缺点:
shiro:
JWT:
1.JWT头
2.有效载荷
3.签名哈希
4.Base64URL算法
二、实现
1.引入maven依赖
2.编写shiro配置类
3.定义token实体继承shiro的token
4.编写token处理工具类
5.自定义shiro的realm
6.JWTfilter过滤器,处理权限验证等
7.登出过滤器
1.基于Token的身份认证是无状态的,服务器或者Session中不会存储任何用户信息-应用程序可以根据需要扩展和添加更多的机器,而不必担心用户登录的位置。
2.支持跨域访问: Cookie是不允许垮域访问的,token支持。
3.解耦: 不需要绑定到一个特定的身份验证方案。Token可以在任何地方生成,只要在 你的API被调用的时候, 你可以进行Token生成调用即可。
4.更适用于移动应用: Cookie不支持手机端访问,token支持。
5.性能: token生成后其实就是一个字符串,在网络传输的过程中,性能更好。
6.基于标准化: 你的API可以采用标准化的 JSON Web Token (JWT). 这个标准已经存在 多个后端库(.NET, Ruby, Java,Python, PHP)和多家公司的支持(如: Firebase,Google, Microsoft)。
1.占带宽
正常情况下要比 session_id 更大,需要消耗更多流量,挤占更多带宽,假如你的网站每月有 10 万次的浏览器,就意味着要多开销几十兆的流量。实际上,许多人会在 JWT 中存储的信息会更多。
2.无法在服务端注销,那么久很难解决劫持问题
3.性能问题
JWT 的卖点之一就是加密签名,由于这个特性,接收方得以验证 JWT 是否有效且被信任。但是大多数 Web 身份认证应用中,JWT 都会被存储到 Cookie 中,这就是说你有了两个层面的签名。为此,你需要花费两倍的 CPU 开销来验证签名。对于有着严格性能要求的 Web 应用,这并不理想,尤其对于单线程环境。
Shiro 是 Java 的一个安全框架。目前Shiro 的使用者有很多,因为它简单、功能够用;它虽然没有 Spring Security 的功能强大,但是在实际工作确实也用不到太多强大的功能,使用小而简单的 Shiro 就够了。Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在 JavaSE 环境,也可以用在 JavaEE 环境。Shiro 可以帮助我们完成:加密、会话管理、认证、授权、与 Web 集成、缓存等。本文主要使用其认证与授权功能;shiro的身份认证:
即在应用中谁能证明他就是他本人。一般提供如他们的身份 ID 一些标识信息来表明他就是他本人,如提供身份证,用户名 / 密码来证明。在 shiro 中,用户需要提供 principals
(身份)和 credentials
(证明)给 shiro,从而应用能验证用户身份:
principals:身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。一个主体可以有多个 principals
,但只有一个 Primary principals
,一般是用户名 / 密码 / 手机号。
credentials:证明 / 凭证,即只有主体知道的安全值,如密码 / 数字证书等。
最常见的 principals
和 credentials
组合就是用户名 / 密码了。
另外两个相关的概念是 Subject
及 Realm
,分别是主体及验证主体的数据源。
Shiro 的 API 也是非常简单;其基本功能点如下图所示:
JWT使用方式:服务器登录成功后创建一个令牌返回给客户端,客户端保存令牌,而服务器不保存令牌,每个请求令牌都被发送回服务器,服务器再对令牌进行处理校验。
JSON Web令牌(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间安全地将信息作为JSON对象传输。由于此信息是经过数字签名的,因此可以被验证和信任。可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对对JWT进行签名。
JWT对象为一个很长的字符串,字符之间通过"."分隔符分为三个子串。注意JWT对象为一个长字串,各字串之间也没有换行符,此处为了演示需要,我们特意分行并用不同颜色表示了。每一个子串表示了一个功能块,总共有以下三个部分:JWT头、有效载荷和签名,将它们写成一行如下:
JWT头部分是一个描述JWT元数据的JSON对象,通常如下所示。
{
"alg": "HS256",
"typ": "JWT"
}
在上面的代码中,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ属性表示令牌的类型,JWT令牌统一写为JWT。
最后,使用Base64 URL算法将上述JSON对象转换为字符串保存。
有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。 JWT指定七个默认字段供选择。
iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT
除以上默认字段外,我们还可以自定义私有字段,如下例:
{
"sex": "男",
"name": "chongchong",
"age": 23
}
默认情况下JWT是未加密的,任何人都可以解读其内容,因此不要构建隐私信息字段,存放保密信息,以防止信息泄露。
JSON对象也使用Base64 URL算法转换为字符串保存。
签名哈希部分是对上面两部分数据签名,通过指定的算法生成哈希,以确保数据不会被篡改。首先,需要指定一个密码(secret)。该密码仅仅为保存在服务器中,并且不能向用户公开。然后,使用标头中指定的签名算法(默认情况下为HMAC SHA256)根据以下公式生成签名。HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload),secret)。在计算出签名哈希后,JWT头,有效载荷和签名哈希的三个部分组合成一个字符串,每个部分用"."分隔,就构成整个JWT对象。
如前所述,JWT头和有效载荷序列化的算法都用到了Base64URL。该算法和常见Base64算法类似,稍有差别。作为令牌的JWT可以放在URL中(例如api.example/?token=xxx)。 Base64中用的三个字符是"+","/"和"=",由于在URL中有特殊含义,因此Base64URL中对他们做了替换:"="去掉,"+"用"-"替换,"/"用"_"替换。
其实太底层的原理,作者也没深入研究,所以不多说废话了,进入搬砖模式吧,本文中贴出代码中有不存在的类,在文末会提供项目地址,可下载项目查看:
org.apache.shiro
shiro-spring
1.4.0
com.auth0
java-jwt
3.4.0
import com.liu.filter.LicenseFilter;
import com.liu.filter.URLPathMatchingFilter;
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.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
/**
* Shiro配置类
*
* @author kevin
* @date 2019/5/18
*/
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置过滤器
shiroFilterFactoryBean.setFilters(filters());
//设置安全权限认证管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/login");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/");
shiroFilterFactoryBean.setUnauthorizedUrl(null);
// 配置映射关系,authc:对应的所有url都必须认证通过才可以访问; anon:对应的所有url都都可以匿名访问
// 必须是LinkedHashMap,因为要保证有序
Map filterChainDefinitionMap = new LinkedHashMap<>();
//swagger的一些资源不拦截
filterChainDefinitionMap.put("/webjars/**", "anon");
filterChainDefinitionMap.put("/swagger-ui.html", "anon");
filterChainDefinitionMap.put("/swagger**/**", "anon");
filterChainDefinitionMap.put("/v2/**", "anon");
//静态资源不被拦截
filterChainDefinitionMap.put("**.js", "anon");
filterChainDefinitionMap.put("/static/**", "anon");
//系统中部分地址不经过过滤器
filterChainDefinitionMap.put("/user/checkToken","anon");
filterChainDefinitionMap.put("/file/**","anon");
filterChainDefinitionMap.put("/user/**","anon");
//配置登录、退出,登录自己实现,登出自定义filter实现(为了实现自己的业务)
filterChainDefinitionMap.put("/user/login","anon");
filterChainDefinitionMap.put("/user/logout","logout");
//加入自定义的过滤器,配置软件授权、JWT与URL的权限过滤器
filterChainDefinitionMap.put("/**", "jwt");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
private Map filters(){
Map filters = new LinkedHashMap<>(4);
//JWT过滤
filters.put("jwt", new JwtFilter());
return filters;
}
/**
* 软件授权 拦截器
* @return com.liu.filter.LicenseFilter
*/
private LicenseFilter getLicenseFilter() {
return new LicenseFilter();
}
/**
* 访问 权限 拦截器
* @return com.liu.filter.URLPathMatchingFilter
*/
private URLPathMatchingFilter getURLPathMatchingFilter() {
return new URLPathMatchingFilter();
}
/**
* shiro身份认证realm,(自己实现:帐号密码校验、权限等)
* @author : 64998
* @date : 2019/5/20
* @return : com.liu.shiro.MyRealm
*/
@Bean
public MyRealm myRealm(){
MyRealm myRealm;
myRealm = new MyRealm();
//自定义的shiro密码校验器:
// myRealm.setCredentialsMatcher(new CustomCredentialsMatcher());
return myRealm;
}
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myRealm());
securityManager.setRememberMeManager(rememberMeManager());
//关闭shiro自带的session
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator sessionStorageEvaluator = new DefaultSessionStorageEvaluator();
sessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(sessionStorageEvaluator);
return securityManager;
}
/**
* 记住我
* @author : 64998
* @date : 2019/5/21
* @return : org.apache.shiro.web.mgt.CookieRememberMeManager
*/
@Bean
public CookieRememberMeManager rememberMeManager(){
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
return cookieRememberMeManager;
}
/**
* cookie对象,记住密码实现
* @author : 64998
* @date : 2019/5/20
* @return : org.apache.shiro.web.servlet.SimpleCookie
*/
@Bean
public SimpleCookie rememberMeCookie(){
//这个参数是cookie的名称,对应前段的checkbox的name = rememberMe
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
//cookie记住我,生效时间,30天,单位秒
simpleCookie.setMaxAge(259200);
return simpleCookie;
}
/**
* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),
* 需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
* 与authorizationAttributeSourceAdvisor一起使用,否者shiro的注解不会生效
* 相当于切面aop
* @author kevin
* @return org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator
* @date 2021/3/6 14:32
*/
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
// 强制使用cglib,防止重复代理和可能引起代理出错的问题
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
/**
* 开启shiro aop注解支持,使用代理方式;所以需要开启代码支持;
* 相当于切入点
* @param securityManager :
* @return org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* shiro生命周期处理器
* @author : kevin
* @date : 2019/5/20
* @return : org.apache.shiro.spring.LifecycleBeanPostProcessor
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
return new LifecycleBeanPostProcessor();
}
/**
* 异常处理
* @author kevin
* @return org.springframework.web.servlet.handler.SimpleMappingExceptionResolver
* @date 2021/3/6 15:54
*/
@Bean(name="simpleMappingExceptionResolver")
public SimpleMappingExceptionResolver createSimpleMappingExceptionResolver() {
SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver();
Properties mappings = new Properties();
mappings.setProperty("DatabaseException", "databaseError");//数据库异常处理
mappings.setProperty("UnauthorizedException","403");
r.setExceptionMappings(mappings); // None by default
r.setDefaultErrorView("error"); // No default
r.setExceptionAttribute("ex"); // Default is "exception"
//r.setWarnLogCategory("example.MvcLogger"); // No default
return r;
}
}
import org.apache.shiro.authc.UsernamePasswordToken;
import java.io.Serializable;
public class JwtToken extends UsernamePasswordToken implements Serializable {
private String token;
public JwtToken(String token) {
this.token = token;
}
@Override
public Object getPrincipal() {
return token;
}
@Override
public Object getCredentials() {
return token;
}
}
此处token增加了sessionInfoMap,作用是将token与超时时间缓存到java内存,解决token本身无法续期问题。但是如果是分布式部署就无法保证token的有效性,可以将缓存中存放信息改存到redis中;如果数据库使用的一个的话,也可以存放到数据库中。
import com.alibaba.fastjson.JSONObject;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.apache.commons.lang.time.DateUtils;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class JwtUtil {
private static final int EXPIRE_TIME = 30;//分钟
private static final int TOKEN_MAX_EXPIRE_TIME = 24*60;//分钟
private static final String CLAIM_LOGIN_ID = "loginId";
private static final String CLAIM_USER = "user";
private static final ConcurrentMap sessionInfoMap = new ConcurrentHashMap<>();
/**
* 校验token是否正确
* @author liuyong
* @param token :
* @param loginId :
* @param secret :
* @param sysUser :
* @return boolean
* @date 2020/12/23 16:41
*/
public static boolean verify(String token, String loginId, String secret, JSONObject sysUser) {
//根据密码生成JWT效验器
Date now = new Date();
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm)
.withClaim(CLAIM_LOGIN_ID, loginId)
.withClaim(CLAIM_USER, JSONObject.toJSONString(sysUser))
.build();
//效验TOKEN
Date expireDate = (Date)sessionInfoMap.get(token);
boolean afterNow = null != expireDate && expireDate.after(now);
try {
verifier.verify(token);
}catch (TokenExpiredException e){
if(!afterNow){
//如果session超时,删除缓存中的session超时时间
sessionInfoMap.remove(token);
throw e;
}
}
if(!afterNow){
sessionInfoMap.remove(token);
throw new TokenExpiredException(String.format("令牌已经在 %s 超时。", expireDate));
}
return true;
}
/**
* 获得token中的信息无需secret解密也能获得
* @author liuyong
* @param token :
* @return java.lang.String
* @date 2020/12/23 16:41
*/
public static String getLoginId(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim(CLAIM_LOGIN_ID).asString();
} catch (JWTDecodeException e) {
return "";
}
}
/**
* 生成签名,30min后过期
* @author liuyong
* @param loginId :
* @param secret :
* @param sysUser :
* @return java.lang.String
* @date 2020/12/23 16:41
*/
public static String sign(String loginId, String secret, JSONObject sysUser) {
Date now = new Date();
Date date = DateUtils.addMinutes(now, EXPIRE_TIME);
Date tokenDate = DateUtils.addMinutes(now, TOKEN_MAX_EXPIRE_TIME);
Algorithm algorithm = Algorithm.HMAC256(secret);
// 附带username信息
String token = JWT.create()
.withClaim(CLAIM_LOGIN_ID, loginId)
.withClaim(CLAIM_USER, JSONObject.toJSONString(sysUser))
.withExpiresAt(tokenDate)
.withIssuedAt(new Date())
.sign(algorithm);
sessionInfoMap.put(token, date);
return token;
}
/**
* 刷新token超时时间
* @author liuyong
* @param token :
* @date 2020/12/24 10:13
*/
public static void refreshToken(String token){
Date now = new Date();
Date date = DateUtils.addMinutes(now, EXPIRE_TIME);
sessionInfoMap.put(token, date);
}
/**
* 获取token超时时间
* @author liuyong
* @param token :
* @return java.util.Date
* @date 2020/12/24 11:35
*/
public static Date getTokenExpireDate(String token){
return (Date)sessionInfoMap.get(token);
}
/**
* 清理已超时的session信息
* @author liuyong
* @date 2020/12/24 13:34
*/
public static void cleanSessionInfo(){
Date now = new Date();
for (Map.Entry entry : sessionInfoMap.entrySet()) {
Date expireDate = (Date) entry.getValue();
if (!expireDate.after(now)) {
sessionInfoMap.remove(entry.getKey());
}
}
}
/**
* 根据token清除session信息
* @author liuyong
* @param token :
* @date 2020/12/24 17:16
*/
public static void cleanSessionInfo(String token) {
sessionInfoMap.remove(token);
}
}
import com.alibaba.fastjson.JSONObject;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.liu.domain.menu.Menu;
import com.liu.domain.role.Role;
import com.liu.service.manage.ManageService;
import com.liu.utils.ThreadLocals;
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 org.springframework.util.StringUtils;
import java.util.List;
/**
* 自己实现登录授权处理
*
* @author kevin
* @date 2019/5/18
*/
public class MyRealm extends AuthorizingRealm{
@Autowired
@Lazy
private ManageService manageService;
/**
* 必须重写此方法,不然Shiro会报错
*/
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof JwtToken;
}
/**
* 角色权限和对应权限添加
* @author : 64998
* @date : 2019/6/27
* @param principals :
* @return : org.apache.shiro.authz.AuthorizationInfo
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String tokenStr = principals.toString();
String loginId = JwtUtil.getLoginId(tokenStr);
JSONObject user = manageService.getUserByLoginId(loginId);
List roles = manageService.getRoleByUserId(user.getInteger("uerId"));
SimpleAuthorizationInfo simpleAuthenticationInfo = new SimpleAuthorizationInfo();
for(Role role:roles){
simpleAuthenticationInfo.addRole(role.getRoleType());
List
import com.liu.enums.ResponseState;
import com.liu.vo.resp.common.ResponseVo;
import lombok.extern.slf4j.Slf4j;
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;
/**
* JWT过滤器
* @author 64998
* @date 2019/5/21
**/
@Slf4j
public class JwtFilter extends BasicHttpAuthenticationFilter {
/**
* 请求是否已经登录(携带token)
*/
@Override
protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
HttpServletRequest req = (HttpServletRequest) request;
String authorization = req.getHeader("token");
return authorization != null;
}
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
//每次都进入 executeLogin 方法执行登入,检查 token 是否正确
try {
executeLogin(request, response);
return true;
} catch (Exception e) {
//token校验失败后,返回false调用onAccessDenied
String msg;
if(e instanceof AuthenticationException){
msg = e.getMessage();
}else{
msg = "令牌无效或已过期,请重新登录!";
}
ResponseVo vo = new ResponseVo.Builder().error().message(msg).build();
HttpServletResponse httpServletResponse = (HttpServletResponse)response;
LoginUtils.responseOutJson(httpServletResponse,vo);
return false;
}
}
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String token = httpServletRequest.getHeader("token");
JwtToken jwtToken = new JwtToken(token);
// 提交给realm进行登入,如果错误他会抛出异常并被捕获
getSubject(request, response).login(jwtToken);
jwtToken.setRememberMe(true);
return true;
}
/**
* 如果权限验证失败,则进入此方法
* @author : 64998
* @date : 2019/5/23
* @param request:
* @param response:
* @return : boolean
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
ResponseVo vo = new ResponseVo();
vo.setCode(ResponseState.LOGIN_ERROR.getCode());
vo.setMsg("Token令牌无效或已过期,请重新登录!");
HttpServletResponse httpServletResponse = (HttpServletResponse)response;
LoginUtils.responseOutJson(httpServletResponse,vo);
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", "*");
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);
}
/**
* 处理非法请求返回
* @author : 64998
* @date : 2019/5/23
* @param request:
* @param response:
* @param code:
* @param msg:
* @return : void
*/
private void response401(ServletRequest request, ServletResponse response,int code,String msg) {
try {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setStatus(HttpStatus.OK.value());
response.setContentType("application/json;charset=utf-8");
httpResponse.getWriter().write("{\"code\":" + code + ", \"msg\":\"" + msg + "\"}");
} catch (IOException e) {
log.error(e.getMessage());
}
}
}
import com.liu.vo.resp.common.ResponseVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.LogoutFilter;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* JWT登出重写
* @author 64998
* @Date 2019/5/21
**/
@Slf4j
public class JwtLogoutFilter extends LogoutFilter {
public JwtLogoutFilter(){
}
/**
* 自定义登出,登出之后,清理当前用户缓存信息
* @param request
* @param response
* @return
* @throws Exception
*/
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
// 登出操作 subject.logout() 可以自动清理缓存信息,
JwtUtil.cleanSessionInfo(((HttpServletRequest)request).getHeader("token"));
//这些代码是可以省略的 这里只是做个笔记 表示这种方式也可以清除
Subject subject = getSubject(request,response);
DefaultWebSecurityManager securityManager = (DefaultWebSecurityManager) SecurityUtils.getSecurityManager();
MyRealm myRealm = (MyRealm) securityManager.getRealms().iterator().next();
PrincipalCollection principals = subject.getPrincipals();
myRealm.clearCache(principals);
//清除cookie
Cookie cookie = new Cookie("token",null);
cookie.setMaxAge(0);
((HttpServletResponse)response).addCookie(cookie);
//登出
subject.logout();
ResponseVo vo = new ResponseVo();
vo.setCode(200);
vo.setMsg("登出成功!");
HttpServletResponse httpServletResponse = (HttpServletResponse)response;
LoginUtils.responseOutJson(httpServletResponse,vo);
return false;
}
}
项目地址:https://download.csdn.net/download/liu649983697/11227253