spring cloud oauth2 實現(xiàn)用戶認證登錄的示例代碼
需求
在微服務架構中,我們有很多業(yè)務模塊,每個模塊都需要有用戶認證,權限校驗。有時候也會接入來自第三方廠商的應用。要求是只登錄一次,即可在各個服務的授權范圍內進行操作??吹竭@個需求,立馬就想到了這不就是單點登錄嗎?于是基于這樣的需求,作者使用spring-cloud-oauth2去簡單的實現(xiàn)了下用戶認證和單點登錄。
相關介紹
OAuth2
OAuth2是一個關于授權的網絡標準,他定制了設計思路和執(zhí)行流程。OAuth2一共有四種授權模式:授權碼模式(authorization code)、簡化模式(implicit)、密碼模式(resource owner password)和客戶端模式(client credentials)。數(shù)據的所有者告訴系統(tǒng)同意授權第三方應用進入系統(tǒng),獲取這些數(shù)據。于是數(shù)據所有者生產了一個短時間內有效的授權碼(token)給第三方應用,用來代替密碼,供第三方使用。具體流程請看下圖,具體的OAuth2介紹,可以參考這篇文章,寫的很詳細。(http://chabaoo.cn/article/198292.htm)

Token
令牌(token)和密碼(password)的作用是一樣的,都可以進入系統(tǒng)獲取資源,但是也有幾點不同:
- 令牌是短期的,到期會自動失效,用戶無法修改。密碼是長期的,用戶可以修改,如果不修改,就不會發(fā)生變化。
- 令牌可以被數(shù)據所有者撤銷,令牌會立即失效。密碼一般不允許其他人撤銷,只能被操作權限更高的人或者本人修改/重制。
- 令牌是有權限范圍的,會被數(shù)據所有者授予。
實現(xiàn)的功能
本篇介紹的是通過密碼模式來實現(xiàn)單點登錄的功能。
在微服務架構中,我們的一個應用可能會有很多個服務運行,協(xié)調來處理實際的業(yè)務。這就需要用到單點登錄的技術,來統(tǒng)一認證調取接口的是哪個用戶。那總不能請求一次,就認證一次,這么做肯定是不行的。那么就需要在認證完用戶之后,給這個用戶授權,然后發(fā)一個令牌(token),有效期內用戶請求資源時,就只需要帶上這個標識自己身份的token即可。
架構說明
認證中心:oauth2-oauth-server,OAuth2的服務端,主要完成用戶Token的生成、刷新、驗證等。
微服務:mzh-etl,微服務之一,接收到請求之后回到認證中心(oauth2-oauth-server)去驗證。
代碼實現(xiàn)
使用到的框架是java基礎的spring boot 和spring-cloud-oauth2
認證中心:
1、引入需要的maven包
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
因為spring-cloud-starter-oauth2中包含了spring-cloud-starter-security,所以就不用再單獨引入了,引入redis包是為了使用redis來存儲token。
2、配置application.yml
這里主要用到的是redis的配置,mysql數(shù)據庫的配置暫時沒有用到。
spring: application: name: oauth-server datasource: url: jdbc:mysql://localhost:3306/mzh_oauth?useSSL=false&characterEncoding=UTF-8 username: root password: admin123 driver-class-name: com.mysql.jdbc.Driver hikari: connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000 maximum-pool-size: 9 redis: database: 0 host: localhost port: 6379 jedis: pool: max-active: 8 max-idle: 8 min-idle: 0 timeout: 10000 server: port: 8888 use-forward-headers: true management: endpoint: health: enabled: true
3、spring security 權限配置
需要繼承WebSecurityConfigurerAdapter
/**
* @Author mzh
* @Date 2020/10/24
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomUserDetailsService customUserDetailsService;
/**
* 修改密碼的加密方式
* @return
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception{
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
// 如果使用BCryptPasswordEncoder,這里就必須指定密碼的加密類
auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/oauth/**").permitAll();
}
}
BCryptPasswordEncoder是一個不可逆的密碼加密類,AuthenticationManager是OAuth2的password必須指定的授權管理Bean。
CustomUserDetailsService這個類是被注入進來的,熟悉spring security的同學應該知道,spring security有一個自己的UserdetailsService用于權限校驗時獲取用戶信息,但是很多時候不符合我們的業(yè)務場景,就需要重現(xiàn)實現(xiàn)這個類。
4、實現(xiàn)CustomUserDetailsService
UserDetailsService這個類的核心方法就是loadUserByUsername()方法,他接收一個用戶名,返回一個UserDetails對象。
/**
* @Author mzh
* @Date 2020/10/24
*/
@Component(value = "customUserDetailsService")
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 1. 根據username 去數(shù)據庫查詢 user
// 2.獲取用戶的角色和權限
// 下面是寫死,暫時不和數(shù)據庫交互
if(!(("admin").equals(username))){
throw new UsernameNotFoundException("the user is not found");
}else{
String role = "ADMIN_ROLE";
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority(role));
String password = passwordEncoder.encode("123456");
return new User(username,password,authorities);
}
}
}
這里是在程序中寫死了用戶和權限。賬號:admin,密碼:123456,權限:ADMIN_ROLE(注意是權限,不是角色),實際中應該從數(shù)據庫獲取用戶和相關的權限,然后進行認證。
5、OAuth2 配置
OAuth2配置需要繼承AuthorizationServerConfigurerAdapter類
/**
* @Author mzh
* @Date 2020/10/24
*/
@Configuration
@EnableAuthorizationServer
public class Oauth2Config extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private UserDetailsService customUserDetailsService;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private TokenStore redisTokenStore;
/**
* 對AuthorizationServerEndpointsConfigurer參數(shù)的重寫
* 重寫授權管理Bean參數(shù)
* 重寫用戶校驗
* 重寫token緩存方式
* @param endpointsConfigurer
* @throws Exception
*/
@Override
public void configure(final AuthorizationServerEndpointsConfigurer endpointsConfigurer) throws Exception{
endpointsConfigurer.authenticationManager(authenticationManager)
.userDetailsService(customUserDetailsService)
.tokenStore(redisTokenStore);
}
/**
* 客戶端的參數(shù)的重寫
* 這里是將數(shù)據直接寫入內存,實際應該從數(shù)據庫表獲取
* clientId:客戶端Id
* secret:客戶端的密鑰
* authorizedGrantTypes:授權方式
* authorization_code: 授權碼類型,
* implicit: 隱式授權,
* password: 密碼授權,
* client_credentials: 客戶端授權,
* refresh_token: 通過上面4中方式獲取的刷新令牌獲取的新令牌,
* 注意是獲取token和refresh_token之后,通過refresh_toke刷新之后的令牌
* accessTokenValiditySeconds: token有效期
* scopes 用來限制客戶端訪問的權限,只有在scopes定義的范圍內,才可以正常的換取token
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception{
clients.inMemory()
.and()
.withClient("mzh-etl")
.secret(passwordEncoder.encode("mzh-etl-8888"))
.authorizedGrantTypes("refresh_token","authorization_code","password")
.accessTokenValiditySeconds(3600)
.scopes("all");
}
@Override
public void configure(AuthorizationServerSecurityConfigurer serverSecurityConfigurer) throws Exception{
serverSecurityConfigurer.allowFormAuthenticationForClients();
serverSecurityConfigurer.checkTokenAccess("permitAll()");
serverSecurityConfigurer.tokenKeyAccess("permitAll()");
serverSecurityConfigurer.passwordEncoder(passwordEncoder);
}
}
6、啟動服務
上述步驟完成之后啟動服務,然后觀察IDEA下方的Endpoints中的Mappings,就可以找到相關的認證端口。主要的有以下幾個:
- POST /oauth/authorize 授權碼模式認證授權接口
- GET/POST /oauth/token 獲取 token 的接口
- POST /oauth/check_token 檢查 token 合法性接口
到此,認證中心就算是創(chuàng)建完成了。我們通過idea的REST Client 來請求一個token進行測試。
請求內容如下:
POST http://localhost:8888/oauth/token?grant_type=password&username=admin&password=123456&scope=all Accept: */* Cache-Control: no-cache Authorization: Basic dXNlci1jbGllbnQ6dXNlci1zZWNyZXQtODg4OA==
第一行POST http://localhost:8888/oauth/token?grant_type=password&username=admin&password=123456&scope=all 表示發(fā)起一個POST請求,請求路徑是/oauth/token,請求參數(shù)是grant_type=password表示認證類型是password,username=admin&password=123456表示用戶名是admin,密碼是123456,scope=all是權限相關的,之前在Oauth2Config中配置了scope是all。
第四行表示在請求頭中加入一個字段Authorization,值為Basic空格base64(clientId:clientSecret),我們之前配置的clientId是“meh-etl”,clientSecret是"meh-etl-8888",所以這個值的base64是:bXpoLWV0bDptemgtZXRsLTg4ODg=。
運行請求之后,如果參數(shù)都正確的話,獲取到返回的內容如下:
{
// token值,后面請求接口時都需要帶上的token
"access_token": "b4cb804c-93d2-4635-913c-265ff4f37309",
// token的形式
"token_type": "bearer",
// 快過期時可以用這個換取新的token
"refresh_token": "5cac05f4-158f-4561-ab16-b06c4bfe899f",
// token的過期時間
"expires_in": 3599,
// 權限范圍
"scope": "all"
}
token值過期之后,可以通過refresh_token來換取新的access_token
POST http://localhost:8888/oauth/token?grant_type=refresh_token&refresh_token=706dac10-d48e-4795-8379-efe8307a2282 Accept: */* Cache-Control: no-cache Authorization: Basic dXNlci1jbGllbnQ6dXNlci1zZWNyZXQtODg4OA==
這次grant_type的值為“refresh_token”,refresh_token的值是要過期的token的refresh_token值,也就是之前請求獲取Token的refresh_token值,請求之后會返回一個和獲取token時一樣格式的數(shù)據。
微服務
1、引入需要的maven包
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
2、配置application.yml
spring: application: name: mzh-etl redis: database: 1 host: localhost port: 6379 jedis: pool: max-active: 8 max-idle: 8 min-idle: 0 timeout: 10000 server: port: 8889 security: oauth2: client: # 需要和之前認證中心配置中的一樣 client-id: mzh-etl client-secret: mzh-etl-8888 # 獲取token的地址 access-token-uri: http://localhost:8888/oauth/token resource: id: mzh-etl user-info-uri: user-info authorization: # 檢查token的地址 check-token-access: http://localhost:8888/oauth/check_token
這里的配置一定要仔細,必須和之前認證中心中配置的一樣。
3、資源配置
在OAuth2中接口也稱為資源,資源的權限也就是接口的權限。spring-cloud-oauth2提供了關于資源的注解
@EnableResourceServer
/**
* @Author mzh
* @Date 2020/10/24
*/
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Value("${security.oauth2.client.client-id}")
private String clientId;
@Value("${security.oauth2.client.client-secret}")
private String clientSecret;
@Value("${security.oauth2.authorization.check-token-access}")
private String checkTokenEndpointUrl;
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Bean("redisTokenStore")
public TokenStore redisTokenStore(){
return new RedisTokenStore(redisConnectionFactory);
}
@Bean
public RemoteTokenServices tokenService() {
RemoteTokenServices tokenService = new RemoteTokenServices();
tokenService.setClientId(clientId);
tokenService.setClientSecret(clientSecret);
tokenService.setCheckTokenEndpointUrl(checkTokenEndpointUrl);
return tokenService;
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenServices(tokenService());
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/get/**").authenticated();
}
}
4、創(chuàng)建一個接口
@RestController
public class UserController {
@GetMapping("get")
@PreAuthorize("hasAuthority('ADMIN_ROLE')")
public Object get(Authentication authentication){
authentication.getAuthorities();
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
String token = details.getTokenValue();
return token;
}
}
這個接口就是會返回一個請求他時攜帶的token值,@PreAuthorize會在請求接口時檢查是否用權限“ADMIN_ROLE”(之前認證中心配置的權限)
5、啟動服務
啟動服務,只有當用戶有“ADMIN_ROLE“的時候,才能正確返回,否則返回401未授權
同樣適用REST Client來發(fā)起一個請求:
GET http://localhost:8889/get Accept: */* Cache-Control: no-cache Authorization: bearer b4cb804c-93d2-4635-913c-265ff4f37309
請求路徑是http://localhost:8889/get 然后在請求頭部帶上我們上一步驟獲取到的token,放入到Authorization中,格式是bearer空格token值,如果請求成功,就會把token原樣返回。
到此這篇關于spring cloud oauth2 實現(xiàn)用戶認證登錄的示例代碼的文章就介紹到這了,更多相關spring cloud oauth2 認證登錄內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Java中IO流使用FileWriter寫數(shù)據基本操作詳解
這篇文章主要介紹了Java中IO流FileWriter寫數(shù)據操作,FileWriter類提供了多種寫入字符的方法,包括寫入單個字符、寫入字符數(shù)組和寫入字符串等,它還提供了一些其他的方法,如刷新緩沖區(qū)、關閉文件等,需要的朋友可以參考下2023-10-10
Java+Ajax實現(xiàn)的用戶名重復檢驗功能實例詳解
這篇文章主要介紹了Java+Ajax實現(xiàn)的用戶名重復檢驗功能,結合實例形式詳細分析了java針對用戶名提交的ajax數(shù)據庫查詢與重復檢查功能相關實現(xiàn)技巧與操作注意事項,需要的朋友可以參考下2018-12-12
Mybatis-Plus實現(xiàn)公共字段自動賦值的方法
這篇文章主要介紹了Mybatis-Plus實現(xiàn)公共字段自動賦值的方法,涉及到通用字段自動填充的最佳實踐總結,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-07-07
SpringBoot+Netty實現(xiàn)簡單聊天室的示例代碼
這篇文章主要介紹了如何利用SpringBoot Netty實現(xiàn)簡單聊天室,文中的示例代碼講解詳細,對我們學習SpringBoot有一定幫助,感興趣的同學可以了解一下2022-02-02
JavaWeb?Servlet實現(xiàn)文件上傳與下載功能實例
因自己負責的項目中需要實現(xiàn)文件上傳,所以下面下面這篇文章主要給大家介紹了關于JavaWeb?Servlet實現(xiàn)文件上傳與下載功能的相關資料,需要的朋友可以參考下2022-04-04

