700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > springboot+shiro+jwt实现登录+权限验证

springboot+shiro+jwt实现登录+权限验证

时间:2023-03-07 21:45:30

相关推荐

springboot+shiro+jwt实现登录+权限验证

目录

一、简介:

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.登出过滤器

一、简介:

JWT优点:

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)。

JWT缺点:

1.占带宽

正常情况下要比 session_id 更大,需要消耗更多流量,挤占更多带宽,假如你的网站每月有 10 万次的浏览器,就意味着要多开销几十兆的流量。实际上,许多人会在 JWT 中存储的信息会更多。

2.无法在服务端注销,那么久很难解决劫持问题

3.性能问题

JWT 的卖点之一就是加密签名,由于这个特性,接收方得以验证 JWT 是否有效且被信任。但是大多数 Web 身份认证应用中,JWT 都会被存储到 Cookie 中,这就是说你有了两个层面的签名。为此,你需要花费两倍的 CPU 开销来验证签名。对于有着严格性能要求的 Web 应用,这并不理想,尤其对于单线程环境。

shiro:

Shiro 是 Java 的一个安全框架。目前Shiro 的使用者有很多,因为它简单、功能够用;它虽然没有 Spring Security 的功能强大,但是在实际工作确实也用不到太多强大的功能,使用小而简单的 Shiro 就够了。Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在 JavaSE 环境,也可以用在 JavaEE 环境。Shiro 可以帮助我们完成:加密、会话管理、认证、授权、与 Web 集成、缓存等。本文主要使用其认证与授权功能;shiro的身份认证:

即在应用中谁能证明他就是他本人。一般提供如他们的身份 ID 一些标识信息来表明他就是他本人,如提供身份证,用户名 / 密码来证明。在 shiro 中,用户需要提供principals(身份)和credentials(证明)给 shiro,从而应用能验证用户身份:

principals:身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。一个主体可以有多个principals,但只有一个Primary principals,一般是用户名 / 密码 / 手机号。

credentials:证明 / 凭证,即只有主体知道的安全值,如密码 / 数字证书等。

最常见的principalscredentials组合就是用户名 / 密码了。

另外两个相关的概念是SubjectRealm,分别是主体及验证主体的数据源。

Shiro 的 API 也是非常简单;其基本功能点如下图所示:

JWT:

JWT使用方式:服务器登录成功后创建一个令牌返回给客户端,客户端保存令牌,而服务器不保存令牌,每个请求令牌都被发送回服务器,服务器再对令牌进行处理校验。

JSON Web令牌(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间安全地将信息作为JSON对象传输。由于此信息是经过数字签名的,因此可以被验证和信任。可以使用秘密(使用HMAC算法)或使用RSAECDSA的公钥/私钥对对JWT进行签名

JWT对象为一个很长的字符串,字符之间通过"."分隔符分为三个子串。注意JWT对象为一个长字串,各字串之间也没有换行符,此处为了演示需要,我们特意分行并用不同颜色表示了。每一个子串表示了一个功能块,总共有以下三个部分:JWT头、有效载荷和签名,将它们写成一行如下:

1.JWT头

JWT头部分是一个描述JWT元数据的JSON对象,通常如下所示。

{

"alg": "HS256",

"typ": "JWT"

}

在上面的代码中,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ属性表示令牌的类型,JWT令牌统一写为JWT。

最后,使用Base64 URL算法将上述JSON对象转换为字符串保存。

2.有效载荷

有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。 JWT指定七个默认字段供选择。

iss:发行人

exp:到期时间

sub:主题

aud:用户

nbf:在此之前不可用

iat:发布时间

jti:JWT ID用于标识该JWT

除以上默认字段外,我们还可以自定义私有字段,如下例:

{

"sex": "男",

"name": "chongchong",

"age": 23

}

默认情况下JWT是未加密的,任何人都可以解读其内容,因此不要构建隐私信息字段,存放保密信息,以防止信息泄露。

JSON对象也使用Base64 URL算法转换为字符串保存。

3.签名哈希

签名哈希部分是对上面两部分数据签名,通过指定的算法生成哈希,以确保数据不会被篡改。首先,需要指定一个密码(secret)。该密码仅仅为保存在服务器中,并且不能向用户公开。然后,使用标头中指定的签名算法(默认情况下为HMAC SHA256)根据以下公式生成签名。HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload),secret)。在计算出签名哈希后,JWT头,有效载荷和签名哈希的三个部分组合成一个字符串,每个部分用"."分隔,就构成整个JWT对象。

4.Base64URL算法

如前所述,JWT头和有效载荷序列化的算法都用到了Base64URL。该算法和常见Base64算法类似,稍有差别。作为令牌的JWT可以放在URL中(例如api.example/?token=xxx)。 Base64中用的三个字符是"+","/"和"=",由于在URL中有特殊含义,因此Base64URL中对他们做了替换:"="去掉,"+"用"-"替换,"/"用"_"替换。

二、实现

其实太底层的原理,作者也没深入研究,所以不多说废话了,进入搬砖模式吧,本文中贴出代码中有不存在的类,在文末会提供项目地址,可下载项目查看:

1.引入maven依赖

<!-- shiro --><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.4.0</version></dependency><!--JWT--><dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.4.0</version></dependency>

2.编写shiro配置类

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 /5/18*/@Configurationpublic class ShiroConfig {@Beanpublic 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<String,String> 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<String,Filter> filters(){Map<String,Filter> 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 : /5/20* @return : com.liu.shiro.MyRealm*/@Beanpublic MyRealm myRealm(){MyRealm myRealm;myRealm = new MyRealm();//自定义的shiro密码校验器:// myRealm.setCredentialsMatcher(new CustomCredentialsMatcher());return myRealm;}@Beanpublic SecurityManager securityManager(){DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();securityManager.setRealm(myRealm());securityManager.setRememberMeManager(rememberMeManager());//关闭shiro自带的sessionDefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();DefaultSessionStorageEvaluator sessionStorageEvaluator = new DefaultSessionStorageEvaluator();sessionStorageEvaluator.setSessionStorageEnabled(false);subjectDAO.setSessionStorageEvaluator(sessionStorageEvaluator);return securityManager;}/*** 记住我* @author : 64998* @date : /5/21* @return : org.apache.shiro.web.mgt.CookieRememberMeManager*/@Beanpublic CookieRememberMeManager rememberMeManager(){CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();cookieRememberMeManager.setCookie(rememberMeCookie());return cookieRememberMeManager;}/*** cookie对象,记住密码实现* @author : 64998* @date : /5/20* @return : org.apache.shiro.web.servlet.SimpleCookie*/@Beanpublic SimpleCookie rememberMeCookie(){//这个参数是cookie的名称,对应前段的checkbox的name = rememberMeSimpleCookie 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 /3/6 14:32*/@Beanpublic DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();// 强制使用cglib,防止重复代理和可能引起代理出错的问题defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);return defaultAdvisorAutoProxyCreator;}/*** 开启shiro aop注解支持,使用代理方式;所以需要开启代码支持;* 相当于切入点* @param securityManager :* @return org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor*/@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);return authorizationAttributeSourceAdvisor;}/*** shiro生命周期处理器* @author : kevin* @date : /5/20* @return : org.apache.shiro.spring.LifecycleBeanPostProcessor*/@Beanpublic LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){return new LifecycleBeanPostProcessor();}/*** 异常处理* @author kevin* @return org.springframework.web.servlet.handler.SimpleMappingExceptionResolver* @date /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 defaultr.setDefaultErrorView("error"); // No defaultr.setExceptionAttribute("ex");// Default is "exception"//r.setWarnLogCategory("example.MvcLogger");// No defaultreturn r;}}

3.定义token实体继承shiro的token

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;}@Overridepublic Object getPrincipal() {return token;}@Overridepublic Object getCredentials() {return token;}}

4.编写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 mons.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<String, Object> sessionInfoMap = new ConcurrentHashMap<>();/*** 校验token是否正确* @author liuyong* @param token :* @param loginId :* @param secret :* @param sysUser :* @return boolean* @date /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();//效验TOKENDate 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 /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 /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 /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 /12/24 11:35*/public static Date getTokenExpireDate(String token){return (Date)sessionInfoMap.get(token);}/*** 清理已超时的session信息* @author liuyong* @date /12/24 13:34*/public static void cleanSessionInfo(){Date now = new Date();for (Map.Entry<String, Object> entry : sessionInfoMap.entrySet()) {Date expireDate = (Date) entry.getValue();if (!expireDate.after(now)) {sessionInfoMap.remove(entry.getKey());}}}/*** 根据token清除session信息* @author liuyong* @param token :* @date /12/24 17:16*/public static void cleanSessionInfo(String token) {sessionInfoMap.remove(token);}}

5.自定义shiro的realm

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 /5/18*/public class MyRealm extends AuthorizingRealm{@Autowired@Lazyprivate ManageService manageService;/*** 必须重写此方法,不然Shiro会报错*/@Overridepublic boolean supports(AuthenticationToken token) {return token instanceof JwtToken;}/*** 角色权限和对应权限添加* @author : 64998* @date : /6/27* @param principals :* @return : org.apache.shiro.authz.AuthorizationInfo*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {String tokenStr = principals.toString();String loginId = JwtUtil.getLoginId(tokenStr);JSONObject user = manageService.getUserByLoginId(loginId);List<Role> roles = manageService.getRoleByUserId(user.getInteger("uerId"));SimpleAuthorizationInfo simpleAuthenticationInfo = new SimpleAuthorizationInfo();for(Role role:roles){simpleAuthenticationInfo.addRole(role.getRoleType());List<Menu> menus = manageService.getMenuByRoleId(role.getRoleId());for(Menu menu : menus){simpleAuthenticationInfo.addStringPermission(menu.getUrl());}}return simpleAuthenticationInfo;}/*** 主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。* @author : 64998* @date : /5/18* @param authToken :* @return org.apache.shiro.authc.AuthenticationInfo*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authToken) {String token = (String) authToken.getPrincipal();if (StringUtils.isEmpty(token)) {throw new AuthenticationException("未找到token,请重新登录");}// 解密获得loginId,用于和数据库进行对比// 实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法String loginId = JwtUtil.getLoginId(token);if (StringUtils.isEmpty(loginId)) {throw new AuthenticationException("token失效,请重新登录");}//此处查询自己的用户信息(根据自己的项目做调整)JSONObject userInfo = manageService.getUserByLoginId(loginId);if (userInfo == null) {throw new AuthenticationException("用户不存在!");}JSONObject sysUser = new JSONObject();sysUser.put("loginId", userInfo.get("loginId"));sysUser.put("userName", userInfo.get("userName"));sysUser.put("userId", userInfo.get("userId"));sysUser.put("sex", userInfo.get("sex"));sysUser.put("tel", userInfo.get("tel"));sysUser.put("isEnable", userInfo.get("isEnable"));try {//校验token是否有效,是否超时boolean isValid = JwtUtil.verify(token, loginId, userInfo.getString("password"), sysUser);//token验证成功,设置当前用户信息、更新token超时时间if(isValid) {JwtUtil.refreshToken(token);//此处使用ThreadLocal记录当前用户,没仔细处理,ThreadLocal应该还会有一些小bugThreadLocals.setCurrentUserThl(sysUser);}else{throw new AuthenticationException("token验证失败,请重新登录");}}catch (TokenExpiredException e){throw new AuthenticationException("token已过期,请重新登录");}catch (Exception e){throw new AuthenticationException("用户名或密码错误");}//参数分别为用户帐号、token、realm namereturn new SimpleAuthenticationInfo(loginId, token, getName());}/*** 缓存的清除* @author : 64998* @date : /6/27* @param principals :* @return : void*/@Overrideprotected void clearCache(PrincipalCollection principals) {super.clearCache(principals);}}

6.JWTfilter过滤器,处理权限验证等

import com.liu.enums.ResponseState;import com.liu.mon.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 /5/21**/@Slf4jpublic class JwtFilter extends BasicHttpAuthenticationFilter {/*** 请求是否已经登录(携带token)*/@Overrideprotected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {HttpServletRequest req = (HttpServletRequest) request;String authorization = req.getHeader("token");return authorization != null;}@Overrideprotected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {//每次都进入 executeLogin 方法执行登入,检查 token 是否正确try {executeLogin(request, response);return true;} catch (Exception e) {//token校验失败后,返回false调用onAccessDeniedString 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;}}@Overrideprotected 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 : /5/23* @param request:* @param response:* @return : boolean*/@Overrideprotected 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;}/*** 对跨域提供支持*/@Overrideprotected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {HttpServletRequest httpServletRequest = (HttpServletRequest) request;HttpServletResponse httpServletResponse = (HttpServletResponse) response;httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));httpServletResponse.setHeader("Access-Control-Allow-Methods", "*");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 : /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());}}}

7.登出过滤器

import com.liu.mon.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 /5/21**/@Slf4jpublic class JwtLogoutFilter extends LogoutFilter {public JwtLogoutFilter(){}/*** 自定义登出,登出之后,清理当前用户缓存信息* @param request* @param response* @return* @throws Exception*/@Overrideprotected 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);//清除cookieCookie 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;}}

项目地址:/download/liu649983697/11227253

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。