Spring Security + OAuth2 + JWT 基本使用
點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號”
優(yōu)質(zhì)文章,第一時(shí)間送達(dá)
作者 | MyDistance
來源 | urlify.cn/ZnYBvu
1、什么是 OAuth2
OAuth 是一個(gè)關(guān)于授權(quán)(authorization)的開放網(wǎng)絡(luò)標(biāo)準(zhǔn),使得第三方應(yīng)用可以使用該令牌在限定時(shí)間、限定范圍訪問指定資源。在全世界得到廣泛應(yīng)用,目前的版本是2.0版。
1.1、關(guān)于 OAuth2 的幾個(gè)重要概念:
resource owner: 擁有被訪問資源的用戶user-agent: 一般來說就是瀏覽器client: 第三方應(yīng)用Authorization server: 認(rèn)證服務(wù)器,用來進(jìn)行用戶認(rèn)證并頒發(fā)tokenResource server:資源服務(wù)器,擁有被訪問資源的服務(wù)器,需要通過token來確定是否有權(quán)限訪問
1.2、握手流程
明確概念后,就可以看 OAuth2 的協(xié)議握手流程,摘自RFC6749

(A)用戶打開客戶端以后,客戶端要求用戶給予授權(quán)。
(B)用戶同意給予客戶端授權(quán)。
(C)客戶端使用上一步獲得的授權(quán),向認(rèn)證服務(wù)器申請令牌。
(D)認(rèn)證服務(wù)器對客戶端進(jìn)行認(rèn)證以后,確認(rèn)無誤,同意發(fā)放令牌。
(E)客戶端使用令牌,向資源服務(wù)器申請獲取資源。
(F)資源服務(wù)器確認(rèn)令牌無誤,同意向客戶端開放資源
1.3、授權(quán)模式
oauth2根據(jù)使用場景不同,分成了4種模式
授權(quán)碼模式(authorization code)
簡化模式(implicit)
密碼模式(resource owner password credentials)
客戶端模式(client credentials)
授權(quán)碼模式使用到了回調(diào)地址,是最為復(fù)雜的方式,通常網(wǎng)站中經(jīng)常出現(xiàn)的微博,qq第三方登錄,都會采用這個(gè)形式。簡化模式不常用。
2、配置
使用oauth2保護(hù)你的應(yīng)用,可以分為簡易的分為三個(gè)步驟
配置資源服務(wù)器
配置授權(quán)服務(wù)器
配置spring security
2.1、maven 依賴配置
這里直接引入 spring-cloud oauth2,更加方便之后的拓展。
<!--spring boot-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.13.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<!--spring cloud oauth2-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<!--spring cloud security-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<!--JWT-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
</dependencies>
<!--spring cloud-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2.2、配置授權(quán)服務(wù)器
這里需要進(jìn)行訪問客戶端的配置,并配置授權(quán)類型和access_token轉(zhuǎn)jwtToken。
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailServiceImpl userDetailService;
@Autowired
@Qualifier("jwtTokenStore")
private TokenStore tokenStore;
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Autowired
private JwtTokenEnhancer jwtTokenEnhancer;
/**
* 配置授權(quán)類型
*
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//設(shè)置Jwt內(nèi)容增強(qiáng)
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
List<TokenEnhancer> list = new ArrayList<>();
list.add(jwtTokenEnhancer);
list.add(jwtAccessTokenConverter);
tokenEnhancerChain.setTokenEnhancers(list);
endpoints
//密碼模式必須配置
.authenticationManager(authenticationManager)
//密碼模式必須配置
.userDetailsService(userDetailService)
//accessToken轉(zhuǎn)JwtToken
.tokenStore(tokenStore)
.accessTokenConverter(jwtAccessTokenConverter)
//jwt內(nèi)容增強(qiáng)
.tokenEnhancer(tokenEnhancerChain);
}
/**
* 配置客戶端詳情信息
*
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.
//基于內(nèi)存配置
inMemory()
//客戶端ID
.withClient("client")
//密鑰
.secret(bCryptPasswordEncoder.encode("112233"))
//重定向地址
.redirectUris("http://www.baidu.com")
//授權(quán)范圍
.scopes("all")
//accessToken有效時(shí)間
.accessTokenValiditySeconds(60)
//refreshToken有效時(shí)間
.refreshTokenValiditySeconds(3600)
/**
* 授權(quán)類型
* authorization_code:授權(quán)碼模式
* password:密碼模式
* refresh_token:刷新令牌
*/
.authorizedGrantTypes("authorization_code", "password", "refresh_token");
}
}
2.3、配置資源服務(wù)器
繼承 ResourceServerConfigurerAdapter并添加 @EnableResourceServer注解
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
//攔截所有請求
.anyRequest()
.authenticated()
.and()
//spring secuity提供了requestMatchers接口,等價(jià)于http.authorizeRequests().anyRequest().access("permitAll");
//提供資源,訪問/user需要權(quán)限認(rèn)證
.requestMatchers()
.antMatchers("/user/**");
}
}
2.4、JWT 配置
2.4.1、accessToken 轉(zhuǎn) JwtToken 配置類
主要工作是創(chuàng)建 JwtAccessTokenConverter并設(shè)置密鑰,并注入到 Bean 管理容器中。
/**
* accessToken轉(zhuǎn)JwtToken配置
*/
@Configuration
public class JwtTokenStoreConfig {
@Bean
public JwtTokenStore jwtTokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
//設(shè)置jwt密鑰
jwtAccessTokenConverter.setSigningKey("test_key");
return jwtAccessTokenConverter;
}
@Bean
public JwtTokenEnhancer jwtTokenEnhancer() {
return new JwtTokenEnhancer();
}
}
2.4.2、JwtToken內(nèi)容拓展配置類
當(dāng) accessToken 轉(zhuǎn) jwtToken時(shí),如果想往令牌中加入自定義用戶信息,例如登錄時(shí)間點(diǎn),可以配置以下類:
/**
* JwtToken內(nèi)容拓展配置類
* @author Lin
*/
public class JwtTokenEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
Map<String, Object> map = new HashMap<>();
map.put("enhance", "enhance info");
((DefaultOAuth2AccessToken)oAuth2AccessToken).setAdditionalInformation(map);
return oAuth2AccessToken;
}
}
2.5、配置 spring security
/**
* spring security配置類
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 密碼加密
*
* @return
*/
@Bean
public BCryptPasswordEncoder getPasswordEncode() {
return new BCryptPasswordEncoder();
}
/**
* 接口請求授權(quán)
*
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/oauth/**", "/login/**","/logout/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.permitAll()
.and()
.csrf().disable();
}
@Override
@Bean
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
}
2.6、實(shí)現(xiàn) UserDetailsService
實(shí)現(xiàn) UserDetailService 用于登錄驗(yàn)證,以及密碼模式下需要用到。
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
String password = bCryptPasswordEncoder.encode("123456");
return new User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList("permission1"));
}
}
創(chuàng)建 User實(shí)體類如下(非必須):
public class User implements UserDetails {
private String username;
private String password;
private List<GrantedAuthority> authorities;
public User(String username, String password, List<GrantedAuthority> authorities) {
this.username = username;
this.password = password;
this.authorities = authorities;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}3、運(yùn)行測試
3.1、獲取授權(quán)碼
直接訪問 /oauth/authorize? 接口可以獲得授權(quán)碼
在我的項(xiàng)目中訪問路徑如下:
http://localhost:8080/oauth/authorize?response_type=code&client_id=client&redirect_uri=http://www.baidu.com&scope=all
瀏覽器訪問,跳轉(zhuǎn)到http://localhost:8080/login.html默認(rèn)登錄頁,點(diǎn)擊登錄,授權(quán):

跳轉(zhuǎn)到https://www.baidu.com/?code=XKee3V頁面,XKee3v就是獲得的授權(quán)碼。
3.2、根據(jù)授權(quán)碼模式獲得令牌
利用 postman 測試,訪問
http://localhost:8080/oauth/token
配置 Authorization 信息,即登錄客戶端的賬號和密碼;

配置 Body 信息,grant_type的參數(shù)值是 authorization_code,authorization_code即為授權(quán)碼模式,code即為上文獲得的授權(quán)碼。

配置完后運(yùn)行測試,返回 access_token和 refresh_token,看到 access_token成功轉(zhuǎn)為JwtToken。

3.3、密碼模式
密碼模式比授權(quán)碼模式簡單一點(diǎn),不需要獲得授權(quán)碼,直接忽略上文獲取授權(quán)碼的操作,只需稍微改動配置信息。
Authorization 信息無需改動,修改 Body 信息, grant_type的參數(shù)值改為 password,代表密碼模式,填寫登錄 spring security 的賬號和密碼。

3.4、刷新令牌
在上文中我設(shè)置了 access_token的時(shí)效性為60秒,當(dāng)access_token失效時(shí),需要根據(jù)refresh_token獲取新的令牌。
訪問路徑如下:
http://localhost:8080/oauth/token
Authorization 配置信息如下:

Body 需要配置 grant_type的參數(shù)值為 refresh_token,代表刷新令牌,并填寫refresh_token的參數(shù)值。訪問后即可獲得新的 access_token。

3.5、根據(jù) access_token獲得資源
訪問路徑如下:
http://localhost:8080/user/getCurrentUser
Header 請求頭添加 Authorization 參數(shù),并設(shè)置參數(shù)值為 bearer+空格+ access_token,即可獲得接口返回值。

粉絲福利:Java從入門到入土學(xué)習(xí)路線圖
??????

??長按上方微信二維碼 2 秒
感謝點(diǎn)贊支持下哈 
