JWT(JSON Web Token), 是為了在網絡應用環境間傳遞聲明而執行的一種基于JSON的開放標準((RFC 7519).該token被設計為緊湊且安全的,特別適用于分布式站點的單點登錄(SSO)場景。JWT的聲明一般被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便于從資源服務器獲取資源,也可以增加一些額外的其它業務邏輯所必須的聲明信息,該token也可直接被用于認證,也可被加密。
通常情況下,把API直接暴露出去是風險很大的,不說別的,直接被機器攻擊就喝一壺的。那么一般來說,對API要劃分出一定的權限級別,然后做一個用戶的鑒權,依據鑒權結果給予用戶開放對應的API。目前,比較主流的方案有幾種:
OAuth(開放授權)是一個開放的授權標準,允許用戶讓第三方應用訪問該用戶在某一服務上存儲的私密的資源(如照片,視頻),而無需將用戶名和密碼提供給第三方應用。
OAuth 允許用戶提供一個令牌,而不是用戶名和密碼來訪問他們存放在特定服務提供者的數據。每一個令牌授權一個特定的第三方系統(例如,視頻編輯網站)在特定的時段(例如,接下來的2小時內)內訪問特定的資源(例如僅僅是某一相冊中的視頻)。這樣,OAuth讓用戶可以授權第三方網站訪問他們存儲在另外服務提供者的某些特定信息,而非所有內容
Cookie認證機制就是為一次請求認證在服務端創建一個Session對象,同時在客戶端的瀏覽器端創建了一個Cookie對象;通過客戶端帶上來Cookie對象來與服務器端的session對象匹配來實現狀態管理的。默認的,當我們關閉瀏覽器的時候,cookie會被刪除。但可以通過修改cookie 的expire time使cookie在一定時間內有效,基于session方式認證勢必會對服務器造成一定的壓力(內存存儲),不易于擴展(需要處理分布式session),跨站請求偽造的攻擊(CSRF)
1.相比于session,它無需保存在服務器,不占用服務器內存開銷。
2.無狀態、可拓展性強:比如有3臺機器(A、B、C)組成服務器集群,若session存在機器A上,session只能保存在其中一臺服務器,此時你便不能訪問機器B、C,因為B、C上沒有存放該Session,而使用token就能夠驗證用戶請求合法性,并且我再加幾臺機器也沒事,所以可拓展性好就是這個意思。
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" ] }
一個JWT實際上就是一個字符串,它由三部分組成,頭部、載荷、簽名(上圖依次排序)
JWT Token生成器:https://jwt.io/
- 登陸認證
- 請求認證
無效Token
有效Token
有優點就會有缺點,是否適用應該考慮清楚,而不是技術跟風
TokenProperties 與 application.yml資源的key映射,方便使用
@Configuration@ConfigurationProperties(prefix = "battcn.security.token")public class TokenProperties { /** * {@link com.battcn.security.model.token.Token} token的過期時間 */ private Integer expirationTime; /** * 發行人 */ private String issuer; /** * 使用的簽名KEY {@link com.battcn.security.model.token.Token}. */ private String signingKey; /** * {@link com.battcn.security.model.token.Token} 刷新過期時間 */ private Integer refreshExpTime; // get set ...}
Token生成的類
@Componentpublic 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過期時間,秘鑰,可自行擴展
battcn: security: token: expiration-time: 10 # 分鐘 1440 refresh-exp-time: 30 # 分鐘 2880 issuer: http://blog.battcn.com signing-key: battcn
WebSecurityConfig 是 Spring Security 關鍵配置,在Securrty中基本上可以通過定義過濾器去實現我們想要的功能.
@Configuration@EnableWebSecuritypublic 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() // 因為使用的是JWT,因此這里可以關閉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); }}
由于JWT代碼做了簡單封裝,包含內容較多,所以文章里只貼主要片段,需要完整代碼可以直接從GIT獲取
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VeVb武林網。
新聞熱點
疑難解答
圖片精選