日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 前端技术 > javascript >内容正文

javascript

Springboot系列之Shiro、JWT、Redis 进行认证鉴权

發(fā)布時(shí)間:2025/3/12 javascript 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Springboot系列之Shiro、JWT、Redis 进行认证鉴权 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

Springboot系列之Shiro、JWT、Redis 進(jìn)行認(rèn)證鑒權(quán)

Shiro架構(gòu)

Apache Shiro是一個(gè)輕量級(jí)的安全框架

Shiro可以非常容易的開發(fā)出足夠好的應(yīng)用,其不僅可以用在JavaSE環(huán)境,也可以用在JavaEE環(huán)境。 Shiro可以幫助我們完成:認(rèn)證、授權(quán)、加密、會(huì)話管理、與Web集成、緩存等。其基本功能點(diǎn)如下圖所示:

  • Authentication:身份認(rèn)證/登錄,驗(yàn)證用戶是不是擁有相應(yīng)的身份;
  • Authorization:授權(quán),即權(quán)限驗(yàn)證,驗(yàn)證某個(gè)已認(rèn)證的用戶是否擁有某個(gè)權(quán)限;即判斷用戶是否能做事情,常見的如:驗(yàn)證某個(gè)用戶是否擁有某個(gè)角色。或者細(xì)粒度的驗(yàn)證某個(gè)用戶對(duì)某個(gè)資源是否具有某個(gè)權(quán)限;
  • Session Manager:會(huì)話管理,即用戶登錄后就是一次會(huì)話,在沒有退出之前,它的所有信息都在會(huì)話中;會(huì)話可以是普通JavaSE環(huán)境的,也可以是如Web環(huán)境的;
  • Cryptography:加密,保護(hù)數(shù)據(jù)的安全性,如密碼加密存儲(chǔ)到數(shù)據(jù)庫(kù),而不是明文存儲(chǔ);
  • Web Support:Web支持,可以非常容易的集成到Web環(huán)境;
  • Caching:緩存,比如用戶登錄后,其用戶信息、擁有的角色/權(quán)限不必每次去查,這樣可以提高效率;
  • Concurrency:shiro支持多線程應(yīng)用的并發(fā)驗(yàn)證,即如在一個(gè)線程中開啟另一個(gè)線程,能把權(quán)限自動(dòng)傳播過去;
  • Testing:提供測(cè)試支持;
  • Run As:允許一個(gè)用戶假裝為另一個(gè)用戶(如果他們?cè)试S)的身份進(jìn)行訪問;
  • Remember Me:記住我,這個(gè)是非常常見的功能,即一次登錄后,下次再來的話不用登錄了。

Shiro不會(huì)去維護(hù)用戶、維護(hù)權(quán)限;這些需要我們自己去設(shè)計(jì)/提供;然后通過相應(yīng)的接口注入給Shiro即可。

接下來我們分別從外部和內(nèi)部來看看Shiro的架構(gòu),對(duì)于一個(gè)好的框架,從外部來看應(yīng)該具有非常簡(jiǎn)單易于使用的API, 且API契約明確;從內(nèi)部來看的話,其應(yīng)該有一個(gè)可擴(kuò)展的架構(gòu),即非常容易插入用戶自定義實(shí)現(xiàn),因?yàn)槿魏慰蚣芏疾荒軡M足所有需求。

可以看到:應(yīng)用代碼直接交互的對(duì)象是Subject,也就是說Shiro的對(duì)外API核心就是Subject。

  • Subject:主體,代表了當(dāng)前“用戶”,這個(gè)用戶不一定是一個(gè)具體的人,與當(dāng)前應(yīng)用交互的任何東西都是Subject,如網(wǎng)絡(luò)爬蟲,機(jī)器人等;即一個(gè)抽象概念;所有Subject都綁定到SecurityManager,與Subject的所有交互都會(huì)委托給SecurityManager;可以把Subject認(rèn)為是一個(gè)門面;SecurityManager才是實(shí)際的執(zhí)行者;
  • SecurityManager:安全管理器;即所有與安全有關(guān)的操作都會(huì)與SecurityManager交互;且它管理著所有Subject;可以看出它是Shiro的核心,它負(fù)責(zé)與后邊介紹的其他組件進(jìn)行交互,如果學(xué)習(xí)過SpringMVC,你可以把它看成DispatcherServlet前端控制器;
  • Realm:域,Shiro從從Realm獲取安全數(shù)據(jù)(如用戶、角色、權(quán)限),就是說SecurityManager要驗(yàn)證用戶身份,那么它需要從Realm獲取相應(yīng)的用戶進(jìn)行比較以確定用戶身份是否合法;也需要從Realm得到用戶相應(yīng)的角色/權(quán)限進(jìn)行驗(yàn)證用戶是否能進(jìn)行操作;可以把Realm看成DataSource,即安全數(shù)據(jù)源。

也就是說對(duì)于我們而言,最簡(jiǎn)單的一個(gè)Shiro應(yīng)用:

  • 應(yīng)用代碼通過Subject來進(jìn)行認(rèn)證和授權(quán),而Subject又委托給SecurityManager;
  • 我們需要給Shiro的SecurityManager注入Realm,從而讓SecurityManager能得到合法的用戶及其權(quán)限進(jìn)行判斷。
  • 從以上也可以看出,Shiro不提供維護(hù)用戶/權(quán)限,而是通過Realm讓開發(fā)人員自己注入。

    接下來我們來從Shiro內(nèi)部來看下Shiro的架構(gòu),如下圖所示:

    • Subject:主體,可以看到主體可以是任何可以與應(yīng)用交互的“用戶”;
    • SecurityManager:相當(dāng)于SpringMVC中的DispatcherServlet或者Struts2中的FilterDispatcher;是Shiro的心臟;所有具體的交互都通過SecurityManager進(jìn)行控制;它管理著所有Subject、且負(fù)責(zé)進(jìn)行認(rèn)證和授權(quán)、及會(huì)話、緩存的管理。
    • Authenticator:認(rèn)證器,負(fù)責(zé)主體認(rèn)證的,這是一個(gè)擴(kuò)展點(diǎn),如果用戶覺得Shiro默認(rèn)的不好,可以自定義實(shí)現(xiàn);其需要認(rèn)證策略(Authentication Strategy),即什么情況下算用戶認(rèn)證通過了;
    • Authorizer:授權(quán)器,或者訪問控制器,用來決定主體是否有權(quán)限進(jìn)行相應(yīng)的操作;即控制著用戶能訪問應(yīng)用中的哪些功能;
    • Realm:可以有1個(gè)或多個(gè)Realm,可以認(rèn)為是安全實(shí)體數(shù)據(jù)源,即用于獲取安全實(shí)體的;可以是JDBC實(shí)現(xiàn),也可以是LDAP實(shí)現(xiàn),或者內(nèi)存實(shí)現(xiàn)等等;由用戶提供;注意:Shiro不知道你的用戶/權(quán)限存儲(chǔ)在哪及以何種格式存儲(chǔ);所以我們一般在應(yīng)用中都需要實(shí)現(xiàn)自己的Realm;
    • SessionManager:如果寫過Servlet就應(yīng)該知道Session的概念,Session呢需要有人去管理它的生命周期,這個(gè)組件就是SessionManager;而Shiro并不僅僅可以用在Web環(huán)境,也可以用在如普通的JavaSE環(huán)境、EJB等環(huán)境;所有呢,Shiro就抽象了一個(gè)自己的Session來管理主體與應(yīng)用之間交互的數(shù)據(jù);這樣的話,比如我們?cè)赪eb環(huán)境用,剛開始是一臺(tái)Web服務(wù)器;接著又上了臺(tái)EJB服務(wù)器;這時(shí)想把兩臺(tái)服務(wù)器的會(huì)話數(shù)據(jù)放到一個(gè)地方,這個(gè)時(shí)候就可以實(shí)現(xiàn)自己的分布式會(huì)話(如把數(shù)據(jù)放到Memcached服務(wù)器);
    • SessionDAO:DAO大家都用過,數(shù)據(jù)訪問對(duì)象,用于會(huì)話的CRUD,比如我們想把Session保存到數(shù)據(jù)庫(kù),那么可以實(shí)現(xiàn)自己的SessionDAO,通過如JDBC寫到數(shù)據(jù)庫(kù);比如想把Session放到Memcached中,可以實(shí)現(xiàn)自己的Memcached SessionDAO;另外SessionDAO中可以使用Cache進(jìn)行緩存,以提高性能;
    • CacheManager:緩存控制器,來管理如用戶、角色、權(quán)限等的緩存的;因?yàn)檫@些數(shù)據(jù)基本上很少去改變,放到緩存中后可以提高訪問的性能
    • Cryptography:密碼模塊,Shiro提高了一些常見的加密組件用于如密碼加密/解密的。

    下面就開始代碼實(shí)現(xiàn)Springboot Shiro JWT Redis認(rèn)證鑒權(quán)(核心代碼如下)

  • Maven依賴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><groupId>org.example</groupId><artifactId>springboot-shrio-jwt</artifactId><version>1.0-SNAPSHOT</version><parent><artifactId>spring-boot-parent</artifactId><groupId>org.springframework.boot</groupId><version>2.1.3.RELEASE </version></parent><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><!-- mybatis-plus --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.1.2</version></dependency><!-- mysql --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><!-- spring熱部署 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>compile</scope><optional>true</optional></dependency><!-- lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.4</version></dependency><!-- druid --><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.17</version></dependency><!-- fastjson --><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.58</version></dependency><!--shiro--><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring-boot-starter</artifactId><version> 1.4.1</version></dependency><!--JWT--><dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.7.0</version></dependency><!-- Redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><source>1.8</source><target>1.8</target></configuration></plugin></plugins></build> </project>
  • 核心配置類
  • package com.kongliand.shiro.config;import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;import com.baomidou.mybatisplus.extension.plugins.PerformanceInterceptor;import org.mybatis.spring.annotation.MapperScan;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;/*** mybatis-plus配置類*/@Configuration@MapperScan(value = {"com.kongliand.shiro.mapper"})public class MybatisPlusConfig {/*** 分頁(yè)插件*/@Beanpublic PaginationInterceptor paginationInterceptor() {return new PaginationInterceptor();}/*** mybatis-plus SQL執(zhí)行效率插件【生產(chǎn)環(huán)境可以關(guān)閉】*/@Beanpublic PerformanceInterceptor performanceInterceptor() {return new PerformanceInterceptor();}}

    ?

    package com.kongliand.shiro.config;import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.interceptor.KeyGenerator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.cache.RedisCacheWriter; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.*;import javax.annotation.Resource; import java.lang.reflect.Method;import java.time.Duration; import java.util.Arrays;import static java.util.Collections.singletonMap;/*** redis核心配置類*/ @Configuration @EnableCaching // 開啟緩存支持 public class RedisConfig extends CachingConfigurerSupport {@Resourceprivate LettuceConnectionFactory lettuceConnectionFactory;/*** 自定義策略生成的key* 自定義的緩存key的生成策略 若想使用這個(gè)key* 只需要講注解上keyGenerator的值設(shè)置為keyGenerator即可</br>*/@Override@Beanpublic KeyGenerator keyGenerator() {return new KeyGenerator() {@Overridepublic Object generate(Object target, Method method, Object... params) {StringBuilder sb = new StringBuilder();sb.append(target.getClass().getName());sb.append(method.getDeclaringClass().getName());Arrays.stream(params).map(Object::toString).forEach(sb::append);return sb.toString();}};}/*** RedisTemplate配置*/@Beanpublic RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {// 設(shè)置序列化Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, Visibility.ANY);om.enableDefaultTyping(DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(om);// 配置redisTemplateRedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();redisTemplate.setConnectionFactory(lettuceConnectionFactory);RedisSerializer<?> stringSerializer = new StringRedisSerializer();redisTemplate.setKeySerializer(stringSerializer);// key序列化redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);// value序列化redisTemplate.setHashKeySerializer(stringSerializer);// Hash key序列化redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);// Hash value序列化redisTemplate.afterPropertiesSet();return redisTemplate;}/*** 緩存配置管理器*/@Beanpublic CacheManager cacheManager(LettuceConnectionFactory factory) {// 配置序列化RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(1));RedisCacheConfiguration redisCacheConfiguration = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));// 以鎖寫入的方式創(chuàng)建RedisCacheWriter對(duì)象//RedisCacheWriter writer = RedisCacheWriter.lockingRedisCacheWriter(factory);// 創(chuàng)建默認(rèn)緩存配置對(duì)象/* 默認(rèn)配置,設(shè)置緩存有效期 1小時(shí)*///RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(1));/* 配置test的超時(shí)時(shí)間為120s*/RedisCacheManager cacheManager = RedisCacheManager.builder(RedisCacheWriter.lockingRedisCacheWriter(factory)).cacheDefaults(redisCacheConfiguration).withInitialCacheConfigurations(singletonMap("test", RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(120)).disableCachingNullValues())).transactionAware().build();return cacheManager;}} package com.kongliand.shiro.config;import com.kongliand.shiro.filter.JwtFilter; import com.kongliand.shiro.shiro.ShiroRealm; 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.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;/*** @desc: shiro 配置類* @author kevin*/@Configuration public class ShiroConfig {/*** Filter Chain定義說明* <p>* 1、一個(gè)URL可以配置多個(gè)Filter,使用逗號(hào)分隔* 2、當(dāng)設(shè)置多個(gè)過濾器時(shí),全部驗(yàn)證通過,才視為通過* 3、部分過濾器可指定參數(shù),如perms,roles*/@Bean("shiroFilter")public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();shiroFilterFactoryBean.setSecurityManager(securityManager);// 攔截器Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();// 配置不會(huì)被攔截的鏈接 順序判斷filterChainDefinitionMap.put("/sys/login", "anon"); //登錄接口排除filterChainDefinitionMap.put("/sys/logout", "anon"); //登出接口排除filterChainDefinitionMap.put("/", "anon");filterChainDefinitionMap.put("/**/*.js", "anon");filterChainDefinitionMap.put("/**/*.css", "anon");filterChainDefinitionMap.put("/**/*.html", "anon");filterChainDefinitionMap.put("/**/*.jpg", "anon");filterChainDefinitionMap.put("/**/*.png", "anon");filterChainDefinitionMap.put("/**/*.ico", "anon");filterChainDefinitionMap.put("/druid/**", "anon");filterChainDefinitionMap.put("/user/test", "anon"); //測(cè)試// 添加自己的過濾器并且取名為jwtMap<String, Filter> filterMap = new HashMap<String, Filter>(1);filterMap.put("jwt", new JwtFilter());shiroFilterFactoryBean.setFilters(filterMap);// <!-- 過濾鏈定義,從上向下順序執(zhí)行,一般將/**放在最為下邊filterChainDefinitionMap.put("/**", "jwt");// 未授權(quán)界面返回JSONshiroFilterFactoryBean.setUnauthorizedUrl("/sys/common/403");shiroFilterFactoryBean.setLoginUrl("/sys/common/403");shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);return shiroFilterFactoryBean;}@Bean("securityManager")public DefaultWebSecurityManager securityManager(ShiroRealm myRealm) {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();securityManager.setRealm(myRealm);/** 關(guān)閉shiro自帶的session,詳情見文檔* http://shiro.apache.org/session-management.html#SessionManagement-* StatelessApplications%28Sessionless%29*/DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();defaultSessionStorageEvaluator.setSessionStorageEnabled(false);subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);securityManager.setSubjectDAO(subjectDAO);return securityManager;}/*** 下面的代碼是添加注解支持** @return*/@Bean@DependsOn("lifecycleBeanPostProcessor")public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);return defaultAdvisorAutoProxyCreator;}@Beanpublic LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {return new LifecycleBeanPostProcessor();}@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();advisor.setSecurityManager(securityManager);return advisor;}}

    3.鑒權(quán)登錄攔截器

    package com.kongliand.shiro.filter;import com.kongliand.shiro.constant.CommonConstant; import com.kongliand.shiro.entity.JwtToken; import lombok.extern.slf4j.Slf4j; import org.apache.shiro.authc.AuthenticationException; 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;/*** 鑒權(quán)登錄攔截器**/ @Slf4j public class JwtFilter extends BasicHttpAuthenticationFilter {/*** 執(zhí)行登錄認(rèn)證** @param request* @param response* @param mappedValue* @return*/@Overrideprotected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {try {executeLogin(request, response);return true;} catch (Exception e) {throw new AuthenticationException(e.getMessage());}}/****/@Overrideprotected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {HttpServletRequest httpServletRequest = (HttpServletRequest) request;String token = httpServletRequest.getHeader(CommonConstant.ACCESS_TOKEN);JwtToken jwtToken = new JwtToken(token);// 提交給realm進(jìn)行登入,如果錯(cuò)誤他會(huì)拋出異常并被捕獲getSubject(request, response).login(jwtToken);// 如果沒有拋出異常則代表登入成功,返回truereturn true;}/*** 對(duì)跨域提供支持*/@Overrideprotected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {HttpServletRequest httpServletRequest = (HttpServletRequest) request;HttpServletResponse httpServletResponse = (HttpServletResponse) response;httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));// 跨域時(shí)會(huì)首先發(fā)送一個(gè)option請(qǐng)求,這里我們給option請(qǐng)求直接返回正常狀態(tài)if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {httpServletResponse.setStatus(HttpStatus.OK.value());return false;}return super.preHandle(request, response);} }

    4.用戶登錄鑒權(quán)和獲取用戶授權(quán)

    package com.kongliand.shiro.shiro;import com.kongliand.shiro.constant.CommonConstant; import com.kongliand.shiro.entity.JwtToken; import com.kongliand.shiro.entity.SysUser; import com.kongliand.shiro.service.ISysUserService; import com.kongliand.shiro.util.CommonUtils; import com.kongliand.shiro.util.JwtUtil; import com.kongliand.shiro.util.RedisUtil; import com.kongliand.shiro.util.SpringContextUtils; import lombok.extern.slf4j.Slf4j; 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.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component;import java.util.Set;/*** 用戶登錄鑒權(quán)和獲取用戶授權(quán)*/ @Component @Slf4j public class ShiroRealm extends AuthorizingRealm {@Autowired@Lazyprivate ISysUserService sysUserService;@Autowired@Lazyprivate RedisUtil redisUtil;/*** 必須重寫此方法,不然Shiro會(huì)報(bào)錯(cuò)*/@Overridepublic boolean supports(AuthenticationToken token) {return token instanceof JwtToken;}/*** 功能: 獲取用戶權(quán)限信息,包括角色以及權(quán)限。只有當(dāng)觸發(fā)檢測(cè)用戶權(quán)限時(shí)才會(huì)調(diào)用此方法,例如checkRole,checkPermission** @param principals token* @return AuthorizationInfo 權(quán)限信息*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {log.info("————權(quán)限認(rèn)證 [ roles、permissions]————");SysUser sysUser = null;String username = null;if (principals != null) {sysUser = (SysUser) principals.getPrimaryPrincipal();username = sysUser.getUserName();}SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();// 設(shè)置用戶擁有的角色集合,比如“admin,test”Set<String> roleSet = sysUserService.getUserRolesSet(username);info.setRoles(roleSet);// 設(shè)置用戶擁有的權(quán)限集合,比如“sys:role:add,sys:user:add”Set<String> permissionSet = sysUserService.getUserPermissionsSet(username);info.addStringPermissions(permissionSet);return info;}/*** 功能: 用來進(jìn)行身份認(rèn)證,也就是說驗(yàn)證用戶輸入的賬號(hào)和密碼是否正確,獲取身份驗(yàn)證信息,錯(cuò)誤拋出異常** @param auth 用戶身份信息 token* @return 返回封裝了用戶信息的 AuthenticationInfo 實(shí)例*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {String token = (String) auth.getCredentials();if (token == null) {log.info("————————身份認(rèn)證失敗——————————IP地址: " + CommonUtils.getIpAddrByRequest(SpringContextUtils.getHttpServletRequest()));throw new AuthenticationException("token為空!");}// 校驗(yàn)token有效性SysUser loginUser = this.checkUserTokenIsEffect(token);return new SimpleAuthenticationInfo(loginUser, token, getName());}/*** 校驗(yàn)token的有效性** @param token*/public SysUser checkUserTokenIsEffect(String token) throws AuthenticationException {// 解密獲得username,用于和數(shù)據(jù)庫(kù)進(jìn)行對(duì)比String username = JwtUtil.getUsername(token);if (username == null) {throw new AuthenticationException("token非法無效!");}// 查詢用戶信息SysUser loginUser = new SysUser();SysUser sysUser = sysUserService.getUserByName(username);if (sysUser == null) {throw new AuthenticationException("用戶不存在!");}// 校驗(yàn)token是否超時(shí)失效 & 或者賬號(hào)密碼是否錯(cuò)誤if (!jwtTokenRefresh(token, username, sysUser.getPassWord())) {throw new AuthenticationException("Token失效請(qǐng)重新登錄!");}// 判斷用戶狀態(tài)if (!"0".equals(sysUser.getDelFlag())) {throw new AuthenticationException("賬號(hào)已被刪除,請(qǐng)聯(lián)系管理員!");}BeanUtils.copyProperties(sysUser, loginUser);return loginUser;}/*** JWTToken刷新生命周期 (解決用戶一直在線操作,提供Token失效問題)* 1、登錄成功后將用戶的JWT生成的Token作為k、v存儲(chǔ)到cache緩存里面(這時(shí)候k、v值一樣)* 2、當(dāng)該用戶再次請(qǐng)求時(shí),通過JWTFilter層層校驗(yàn)之后會(huì)進(jìn)入到doGetAuthenticationInfo進(jìn)行身份驗(yàn)證* 3、當(dāng)該用戶這次請(qǐng)求JWTToken值還在生命周期內(nèi),則會(huì)通過重新PUT的方式k、v都為Token值,緩存中的token值生命周期時(shí)間重新計(jì)算(這時(shí)候k、v值一樣)* 4、當(dāng)該用戶這次請(qǐng)求jwt生成的token值已經(jīng)超時(shí),但該token對(duì)應(yīng)cache中的k還是存在,則表示該用戶一直在操作只是JWT的token失效了,程序會(huì)給token對(duì)應(yīng)的k映射的v值重新生成JWTToken并覆蓋v值,該緩存生命周期重新計(jì)算* 5、當(dāng)該用戶這次請(qǐng)求jwt在生成的token值已經(jīng)超時(shí),并在cache中不存在對(duì)應(yīng)的k,則表示該用戶賬戶空閑超時(shí),返回用戶信息已失效,請(qǐng)重新登錄。* 6、每次當(dāng)返回為true情況下,都會(huì)給Response的Header中設(shè)置Authorization,該Authorization映射的v為cache對(duì)應(yīng)的v值。* 7、注:當(dāng)前端接收到Response的Header中的Authorization值會(huì)存儲(chǔ)起來,作為以后請(qǐng)求token使用** @param userName* @param passWord* @return*/public boolean jwtTokenRefresh(String token, String userName, String passWord) {String cacheToken = String.valueOf(redisUtil.get(CommonConstant.PREFIX_USER_TOKEN + token));if (CommonUtils.isNotEmpty(cacheToken)) {// 校驗(yàn)token有效性if (!JwtUtil.verify(cacheToken, userName, passWord)) {String newAuthorization = JwtUtil.sign(userName, passWord);redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, newAuthorization);// 設(shè)置超時(shí)時(shí)間redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME / 1000);} else {redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, cacheToken);// 設(shè)置超時(shí)時(shí)間redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME / 1000);}return true;}return false;}}

    [外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-Wdbq8hpS-1639299147506)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

    5.application.yml配置信息

    server:port: 8088spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverdruid:url: jdbc:mysql://127.0.0.1:3306/jwt?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTCusername: rootpassword: admin123initial-size: 10max-active: 100min-idle: 10max-wait: 60000pool-prepared-statements: truemax-pool-prepared-statement-per-connection-size: 20time-between-eviction-runs-millis: 60000min-evictable-idle-time-millis: 300000#validation-query: SELECT 1 FROM DUALtest-while-idle: truetest-on-borrow: falsetest-on-return: falsestat-view-servlet:enabled: trueurl-pattern: /druid/*login-username: adminlogin-password: adminfilter:stat:log-slow-sql: trueslow-sql-millis: 1000merge-sql: falsewall:config:multi-statement-allow: true#redis配置redis:database: 0host: 127.0.0.1lettuce:pool:max-active: 8 #最大連接數(shù)據(jù)庫(kù)連接數(shù),設(shè) 0 為沒有限制max-idle: 8 #最大等待連接中的數(shù)量,設(shè) 0 為沒有限制max-wait: -1ms #最大建立連接等待時(shí)間。如果超過此時(shí)間將接到異常。設(shè)為-1表示無限制。min-idle: 0 #最小等待連接中的數(shù)量,設(shè) 0 為沒有限制shutdown-timeout: 100mspassword: ''port: 6379#mybatis plus設(shè)置 mybatis-plus:type-aliases-package: com.kongliand.shiro.entitymapper-locations: classpath:mapper/*.xmlglobal-config:banner: falsedb-config:#主鍵類型id-type: auto# 默認(rèn)數(shù)據(jù)庫(kù)表下劃線命名table-underline: trueconfiguration:map-underscore-to-camel-case: true# 這個(gè)配置會(huì)將執(zhí)行的sql打印出來,在開發(fā)或測(cè)試的時(shí)候可以用log-impl: org.apache.ibatis.logging.stdout.StdOutImpl#日志配置 logging:level:com.kongliand.shiro.mapper: debug

    最后開始驗(yàn)證一下
    1.獲取token
    2.根據(jù)token請(qǐng)求接口

    總結(jié)

    以上是生活随笔為你收集整理的Springboot系列之Shiro、JWT、Redis 进行认证鉴权的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。