您好,登錄后才能下訂單哦!
小編給大家分享一下SpringCloud服務(wù)認(rèn)證JWT怎么實(shí)現(xiàn),相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
JWT(JSON Web Token), 是為了在網(wǎng)絡(luò)應(yīng)用環(huán)境間傳遞聲明而執(zhí)行的一種基于JSON的開放標(biāo)準(zhǔn)((RFC 7519).該token被設(shè)計(jì)為緊湊且安全的,特別適用于分布式站點(diǎn)的單點(diǎn)登錄(SSO)場景。JWT的聲明一般被用來在身份提供者和服務(wù)提供者間傳遞被認(rèn)證的用戶身份信息,以便于從資源服務(wù)器獲取資源,也可以增加一些額外的其它業(yè)務(wù)邏輯所必須的聲明信息,該token也可直接被用于認(rèn)證,也可被加密。
通常情況下,把API直接暴露出去是風(fēng)險(xiǎn)很大的,不說別的,直接被機(jī)器攻擊就喝一壺的。那么一般來說,對API要劃分出一定的權(quán)限級別,然后做一個用戶的鑒權(quán),依據(jù)鑒權(quán)結(jié)果給予用戶開放對應(yīng)的API。目前,比較主流的方案有幾種:
OAuth(開放授權(quán))是一個開放的授權(quán)標(biāo)準(zhǔn),允許用戶讓第三方應(yīng)用訪問該用戶在某一服務(wù)上存儲的私密的資源(如照片,視頻),而無需將用戶名和密碼提供給第三方應(yīng)用。
OAuth 允許用戶提供一個令牌,而不是用戶名和密碼來訪問他們存放在特定服務(wù)提供者的數(shù)據(jù)。每一個令牌授權(quán)一個特定的第三方系統(tǒng)(例如,視頻編輯網(wǎng)站)在特定的時(shí)段(例如,接下來的2小時(shí)內(nèi))內(nèi)訪問特定的資源(例如僅僅是某一相冊中的視頻)。這樣,OAuth讓用戶可以授權(quán)第三方網(wǎng)站訪問他們存儲在另外服務(wù)提供者的某些特定信息,而非所有內(nèi)容
Cookie認(rèn)證機(jī)制就是為一次請求認(rèn)證在服務(wù)端創(chuàng)建一個Session對象,同時(shí)在客戶端的瀏覽器端創(chuàng)建了一個Cookie對象;通過客戶端帶上來Cookie對象來與服務(wù)器端的session對象匹配來實(shí)現(xiàn)狀態(tài)管理的。默認(rèn)的,當(dāng)我們關(guān)閉瀏覽器的時(shí)候,cookie會被刪除。但可以通過修改cookie 的expire time使cookie在一定時(shí)間內(nèi)有效,基于session方式認(rèn)證勢必會對服務(wù)器造成一定的壓力(內(nèi)存存儲),不易于擴(kuò)展(需要處理分布式session),跨站請求偽造的攻擊(CSRF)
1.相比于session,它無需保存在服務(wù)器,不占用服務(wù)器內(nèi)存開銷。
2.無狀態(tài)、可拓展性強(qiáng):比如有3臺機(jī)器(A、B、C)組成服務(wù)器集群,若session存在機(jī)器A上,session只能保存在其中一臺服務(wù)器,此時(shí)你便不能訪問機(jī)器B、C,因?yàn)锽、C上沒有存放該Session,而使用token就能夠驗(yàn)證用戶請求合法性,并且我再加幾臺機(jī)器也沒事,所以可拓展性好就是這個意思。
3.前后端分離,支持跨域訪問。
- JWT的組成
{ "iss": "JWT Builder", "iat": 1416797419, "exp": 1448333419, "aud": "www.battcn.com", "sub": "1837307557@qq.com", "GivenName": "Levin", "Surname": "Levin", "Email": "1837307557@qq.com", "Role": [ "ADMIN", "MEMBER" ] }
iss: 該JWT的簽發(fā)者,是否使用是可選的;
sub: 該JWT所面向的用戶,是否使用是可選的;
aud: 接收該JWT的一方,是否使用是可選的;
exp(expires): 什么時(shí)候過期,這里是一個Unix時(shí)間戳,是否使用是可選的;
iat(issued at): 在什么時(shí)候簽發(fā)的(UNIX時(shí)間),是否使用是可選的;
nbf (Not Before):如果當(dāng)前時(shí)間在nbf里的時(shí)間之前,則Token不被接受;一般都會留一些余地,比如幾分鐘;,是否使用是可選的;
一個JWT實(shí)際上就是一個字符串,它由三部分組成,頭部、載荷、簽名(上圖依次排序)
JWT Token生成器:https://jwt.io/
- 登陸認(rèn)證
客戶端發(fā)送 POST 請求到服務(wù)器,提交登錄處理的Controller層
調(diào)用認(rèn)證服務(wù)進(jìn)行用戶名密碼認(rèn)證,如果認(rèn)證通過,返回完整的用戶信息及對應(yīng)權(quán)限信息
利用 JJWT 對用戶、權(quán)限信息、秘鑰構(gòu)建Token
返回構(gòu)建好的Token
- 請求認(rèn)證
客戶端向服務(wù)器請求,服務(wù)端讀取請求頭信息(request.header)獲取Token
如果找到Token信息,則根據(jù)配置文件中的簽名加密秘鑰,調(diào)用JJWT Lib對Token信息進(jìn)行解密和解碼;
完成解碼并驗(yàn)證簽名通過后,對Token中的exp、nbf、aud等信息進(jìn)行驗(yàn)證;
全部通過后,根據(jù)獲取的用戶的角色權(quán)限信息,進(jìn)行對請求的資源的權(quán)限邏輯判斷;
如果權(quán)限邏輯判斷通過則通過Response對象返回;否則則返回HTTP 401;
無效Token
有效Token
有優(yōu)點(diǎn)就會有缺點(diǎn),是否適用應(yīng)該考慮清楚,而不是技術(shù)跟風(fēng)
token過大容易占用更多的空間
token中不應(yīng)該存儲敏感信息
JWT不是 session ,勿將token當(dāng)session
無法作廢已頒布的令牌,因?yàn)樗械恼J(rèn)證信息都在JWT中,由于在服務(wù)端沒有狀態(tài),即使你知道了某個JWT被盜取了,你也沒有辦法將其作廢。在JWT過期之前(你絕對應(yīng)該設(shè)置過期時(shí)間),你無能為力。
類似緩存,由于無法作廢已頒布的令牌,在其過期前,你只能忍受”過期”的數(shù)據(jù)(自己放出去的token,含著淚也要用到底)。
TokenProperties 與 application.yml資源的key映射,方便使用
@Configuration @ConfigurationProperties(prefix = "battcn.security.token") public class TokenProperties { /** * {@link com.battcn.security.model.token.Token} token的過期時(shí)間 */ private Integer expirationTime; /** * 發(fā)行人 */ private String issuer; /** * 使用的簽名KEY {@link com.battcn.security.model.token.Token}. */ private String signingKey; /** * {@link com.battcn.security.model.token.Token} 刷新過期時(shí)間 */ private Integer refreshExpTime; // get set ... }
Token生成的類
@Component public class TokenFactory { private final TokenProperties properties; @Autowired public TokenFactory(TokenProperties properties) { this.properties = properties; } /** * 利用JJWT 生成 Token * @param context * @return */ public AccessToken createAccessToken(UserContext context) { Optional.ofNullable(context.getUsername()).orElseThrow(()-> new IllegalArgumentException("Cannot create Token without username")); Optional.ofNullable(context.getAuthorities()).orElseThrow(()-> new IllegalArgumentException("User doesn't have any privileges")); Claims claims = Jwts.claims().setSubject(context.getUsername()); claims.put("scopes", context.getAuthorities().stream().map(Object::toString).collect(toList())); LocalDateTime currentTime = LocalDateTime.now(); String token = Jwts.builder() .setClaims(claims) .setIssuer(properties.getIssuer()) .setIssuedAt(Date.from(currentTime.atZone(ZoneId.systemDefault()).toInstant())) .setExpiration(Date.from(currentTime .plusMinutes(properties.getExpirationTime()) .atZone(ZoneId.systemDefault()).toInstant())) .signWith(SignatureAlgorithm.HS512, properties.getSigningKey()) .compact(); return new AccessToken(token, claims); } /** * 生成 刷新 RefreshToken * @param userContext * @return */ public Token createRefreshToken(UserContext userContext) { if (StringUtils.isBlank(userContext.getUsername())) { throw new IllegalArgumentException("Cannot create Token without username"); } LocalDateTime currentTime = LocalDateTime.now(); Claims claims = Jwts.claims().setSubject(userContext.getUsername()); claims.put("scopes", Arrays.asList(Scopes.REFRESH_TOKEN.authority())); String token = Jwts.builder() .setClaims(claims) .setIssuer(properties.getIssuer()) .setId(UUID.randomUUID().toString()) .setIssuedAt(Date.from(currentTime.atZone(ZoneId.systemDefault()).toInstant())) .setExpiration(Date.from(currentTime .plusMinutes(properties.getRefreshExpTime()) .atZone(ZoneId.systemDefault()).toInstant())) .signWith(SignatureAlgorithm.HS512, properties.getSigningKey()) .compact(); return new AccessToken(token, claims); } }
配置文件,含token過期時(shí)間,秘鑰,可自行擴(kuò)展
battcn: security: token: expiration-time: 10 # 分鐘 1440 refresh-exp-time: 30 # 分鐘 2880 issuer: http://blog.battcn.com signing-key: battcn
WebSecurityConfig 是 Spring Security 關(guān)鍵配置,在Securrty中基本上可以通過定義過濾器去實(shí)現(xiàn)我們想要的功能.
@Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { public static final String TOKEN_HEADER_PARAM = "X-Authorization"; public static final String FORM_BASED_LOGIN_ENTRY_POINT = "/api/auth/login"; public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/api/**"; public static final String MANAGE_TOKEN_BASED_AUTH_ENTRY_POINT = "/manage/**"; public static final String TOKEN_REFRESH_ENTRY_POINT = "/api/auth/token"; @Autowired private RestAuthenticationEntryPoint authenticationEntryPoint; @Autowired private AuthenticationSuccessHandler successHandler; @Autowired private AuthenticationFailureHandler failureHandler; @Autowired private LoginAuthenticationProvider loginAuthenticationProvider; @Autowired private TokenAuthenticationProvider tokenAuthenticationProvider; @Autowired private TokenExtractor tokenExtractor; @Autowired private AuthenticationManager authenticationManager; protected LoginProcessingFilter buildLoginProcessingFilter() throws Exception { LoginProcessingFilter filter = new LoginProcessingFilter(FORM_BASED_LOGIN_ENTRY_POINT, successHandler, failureHandler); filter.setAuthenticationManager(this.authenticationManager); return filter; } protected TokenAuthenticationProcessingFilter buildTokenAuthenticationProcessingFilter() throws Exception { List<String> list = Lists.newArrayList(TOKEN_BASED_AUTH_ENTRY_POINT,MANAGE_TOKEN_BASED_AUTH_ENTRY_POINT); SkipPathRequestMatcher matcher = new SkipPathRequestMatcher(list); TokenAuthenticationProcessingFilter filter = new TokenAuthenticationProcessingFilter(failureHandler, tokenExtractor, matcher); filter.setAuthenticationManager(this.authenticationManager); return filter; } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override protected void configure(AuthenticationManagerBuilder auth) { auth.authenticationProvider(loginAuthenticationProvider); auth.authenticationProvider(tokenAuthenticationProvider); } @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() // 因?yàn)槭褂玫氖荍WT,因此這里可以關(guān)閉csrf了 .exceptionHandling() .authenticationEntryPoint(this.authenticationEntryPoint) .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers(FORM_BASED_LOGIN_ENTRY_POINT).permitAll() // Login end-point .antMatchers(TOKEN_REFRESH_ENTRY_POINT).permitAll() // Token refresh end-point .and() .authorizeRequests() .antMatchers(TOKEN_BASED_AUTH_ENTRY_POINT).authenticated() // Protected API End-points .antMatchers(MANAGE_TOKEN_BASED_AUTH_ENTRY_POINT).hasAnyRole(RoleEnum.ADMIN.name()) .and() .addFilterBefore(buildLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class) .addFilterBefore(buildTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class); } }
以上是“SpringCloud服務(wù)認(rèn)證JWT怎么實(shí)現(xiàn)”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注億速云行業(yè)資訊頻道!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。