使用OAuth2实现认证服务器和资源服务器
在項目中有用到OAuth2,這里記錄下研究成功。詳細介紹可參考官方文檔:https://tools.ietf.org/html/rfc6749
準備工作:
1、spring-oauth-server 認證服務器和資源服務器(也可以分開)。作為一個jar包提供給客戶端使用
2、spring-security-demo 客戶端。資源所有者,需要依賴spring-oauth-server進行授權認證
spring-oauth-server
pom依賴:
<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>
RedisTokenStore配置:
@Configuration
public class RedisTokenStoreConfig {
@Autowired
private RedisConnectionFactory connectionFactory;
/**
* 配置Token存儲到Redis中
*/
@Bean
public TokenStore redisTokenStore() {
return new RedisTokenStore(connectionFactory);
}
}
兩個配置類SecurityProperty和OAuth2Property:
@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "xwj.security")
public class SecurityProperty {
private OAuth2Property oauth2 = new OAuth2Property();
}
@Getter
@Setter
public class OAuth2Property {
private OAuth2ClientProperty[] clients = {};
}
認證服務器配置:
/**
* 配置認證服務器
*/
@Configuration
@EnableAuthorizationServer // 開啟認證服務
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private SecurityProperty securityProperty;
@Autowired
private TokenStore tokenStore;
/**
* 用來配置授權(authorization)以及令牌(token)的訪問端點和令牌服務(token services)
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore) // 配置存儲token的方式(默認InMemoryTokenStore)
.authenticationManager(authenticationManager) // 密碼模式,必須配置AuthenticationManager,不然不生效
.userDetailsService(userDetailsService); // 密碼模式,這里得配置UserDetailsService
/*
* pathMapping用來配置端點URL鏈接,有兩個參數,都將以 "/" 字符為開始的字符串
*
* defaultPath:這個端點URL的默認鏈接
*
* customPath:你要進行替代的URL鏈接
*/
endpoints.pathMapping("/oauth/token", "/oauth/xwj");
}
/**
* 用來配置客戶端詳情服務(給誰發送令牌)
*/
@Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
InMemoryClientDetailsServiceBuilder builder = clients.inMemory();
OAuth2ClientProperty[] oauth2Clients = securityProperty.getOauth2().getClients();
if (ArrayUtils.isNotEmpty(oauth2Clients)) {
for (OAuth2ClientProperty config : oauth2Clients) {
builder // 使用in-memory存儲
.withClient(config.getClientId()).secret(config.getClientSecret())
.accessTokenValiditySeconds(config.getAccessTokenValiditySeconds()) // 發出去的令牌有效時間(秒)
.authorizedGrantTypes("authorization_code", "client_credentials", "password", "refresh_token") // 該client允許的授權類型
.scopes("all", "read", "write") // 允許的授權范圍(如果是all,則請求中可以不要scope參數,否則必須加上scopes中配置的)
.autoApprove(true); // 自動審核
}
}
}
}
認證服務器端點配置:
1、token模式默認存儲在內存中,服務重啟后就沒了。這里改為使用redis存儲,同時也可用于客戶端擴展集群
2、如果要使用密碼模式,必須得配置AuthenticationManager(原因可查看源碼AuthorizationServerEndpointsConfigurer的getDefaultTokenGranters方法)
3、在使用密碼模式時,如果用戶實現了UserDetailsService類,則在驗證用戶名密碼時,使用自定義的方法。因為在校驗用戶名密碼時,使用了DaoAuthenticationProvider中的retrieveUser方法(具體可參考AuthenticationManager、ProviderManager)
4、默認獲取token的路徑是/oauth/token,通過pathMapping方法,可改變默認路徑
客戶端配置:
1、這里是從配置類中讀取clientId、clientSecret、有效期等,便于擴展
2、authorizedGrantTypes,授權認證類型,這里配置的是授權碼模式、客戶端模式、密碼模式、刷新token模式(還有一種簡化模式,這里不演示)
3、如果不配置autoApprove,那獲取授權碼時,需要手動點一下授權
資源服務器配置:
@Configuration
@EnableResourceServer // 開啟資源服務
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
super.configure(http);
}
}
使用默認的配置,表示對所有資源都需要授權認證,即授權通過后可以訪問所有資源
spring-security-demo
pom依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- druid 數據庫連接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.5</version>
</dependency>
<dependency>
<groupId>com.xwj</groupId>
<artifactId>spring-oauth-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
application.yml配置文件:
server:
port: 80
spring:
application:
name: spring-security-demo #應用程序名稱
#durid 數據庫連接池
datasource:
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://127.0.0.1:3306/xwj?autoReconnect=true&failOverReadOnly=false&createDatabaseIfNotExist=true&useSSL=false&useUnicode=true&characterEncoding=utf8
username: root
password: 123456
jpa:
open-in-view: true
hibernate:
ddl-auto: update
#show-sql: true
properties:
hibernate.dialect: org.hibernate.dialect.MySQL57InnoDBDialect
redis:
database: 2 #Redis數據庫索引(默認為0)
host: localhost #Redis服務器地址
port: 6379
password: ## 密碼(默認為空)
pool:
max-active: 8 #連接池最大連接數(使用負值表示沒有限制)
max-wait: -1 #連接池最大阻塞等待時間(使用負值表示沒有限制)
max-idle: 8 #連接池中的最大空閑連接
logging:
level:
#root: INFO
#org.hibernate: INFO
jdbc: off
jdbc.sqltiming: debug
com:
xwj: debug
xwj:
security:
oauth2:
storeType: redis
jwtSignKey: 1234567890
clients[0]:
clientId: test
clientSecret: testsecret
accessTokenValiditySeconds: 1800
clients[1]:
clientId: myid
clientSecret: mysecret
accessTokenValiditySeconds: 3600
新建UserDetailsService的實現類MyUserDetailServiceImpl類:
/**
* 如果使用密碼模式,需要實現UserDetailsService,用于覆蓋默認的InMemoryUserDetailsManager方法
*
* 可以用來校驗用戶信息,并且可以添加自定義的用戶屬性
*/
@Service
public class MyUserDetailServiceImpl implements UserDetailsService {
@Autowired
private IUserService userService;
/**
* 根據username查詢用戶實體
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 通過用戶名查詢數據
AuthUserInfo userInfo = userService.findByUsername(username);
if (userInfo == null) {
throw new BadCredentialsException("User '" + username + "' not found");
}
// 用戶角色
List<? extends GrantedAuthority> authorities = AuthorityUtils
.commaSeparatedStringToAuthorityList("ROLE_" + userInfo.getRole());
return new SocialUser(username, userInfo.getPassword(), true, true, true, true, authorities);
}
}
新建一個密碼加密的配置類,用來實現PasswordEncoder(默認的加密方式是BCryptPasswordEncoder)
/**
* 加密方式配置
*/
@Configuration
public class PasswordEncoderConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new PasswordEncoder() {
@Override
public String encode(CharSequence rawPassword) {
return DigestUtils.md5DigestAsHex(rawPassword.toString().getBytes());
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return encodedPassword.contentEquals(encode(rawPassword));
}
};
}
}
用戶信息實體:
@Entity
@Getter
@Setter
public class AuthUserInfo {
@Id
@TableGenerator(name = "global_id_gen", allocationSize = 1)
@GeneratedValue(strategy = GenerationType.TABLE, generator = "global_id_gen")
private Long id;
/** 用戶名 */
private String username;
/** 密碼 */
private String password;
/** 角色 */
private String role;
}
數據訪問層,自己實現。造一條用戶數據(角色只能為USER,在獲取授權碼的時候需要擁有該角色的用戶進行表單登錄):
新建一個IndexController:
@RestController
@RequestMapping("index")
public class IndexController {
/**
* 獲取資源
*/
@GetMapping("/getResource")
public String getResource() {
return "OK";
}
/**
* 獲取當前授權用戶
*/
@GetMapping("/me")
public Object getCurrrentUser(@AuthenticationPrincipal UserDetails user) {
return user;
}
}
1、授權碼模式:
1.1、 瀏覽器請求如下地址,獲取授權code(其中response_type=code是固定寫法,scope為權限,state為自定義數據):
http://localhost/oauth/authorize?client_id=test&redirect_uri=http://www.baidu.com&response_type=code&scope=read &state=mystate
1.2、輸入用戶名密碼(xwj/123456):
1.3、上面配置的自動授權,所有會oauth會立馬調用回調地址并返回授權code和state(可以發現state傳的什么就返回什么):
1.4、在獲得授權碼后,接下來獲取訪問令牌。使用postman請求 http://localhost/oauth/xwj:
注意,需要在Authorization里設置Username和Password(就是客戶端配置的clientId和clientSecret),還有TYPE類型:
獲取到的token如下:
1.5、授權碼用一次之后,oauth將會把它從緩存中刪掉,所以只能使用。如果重復使用,將返回:
1.6、如果不帶上token,請求資源:http://localhost/index/getResource,將會返回無權訪問:
1.7、如果帶上token,請求資源:http://localhost/index/getResource,將可以正常獲取資源數據:
1.8、如果token錯誤,將提示無效的token:
2、客戶端模式:
2.1、直接獲取token,請求地址:http://localhost/oauth/xwj
獲取token操作同上。由于客戶端模式每次的參數是一樣的,則請求多次返回同一個token,只是有效期在變小
3、密碼模式:
3.1、直接獲取token,請求地址:http://localhost/oauth/xwj
獲取token操作同上。由于客戶端模式每次的參數是一樣的,則請求多次返回同一個token,只是有效期在變小
4、刷新token:
4.1、以授權碼模式為例,在授權碼的token過期后,使用當時的refresh_token獲取新的token:
4.2、獲取到新的token,就可以正常訪問資源了:
使用redis存儲token,打開Redis Desktop Manager工具,可以看到數據結構如下:
至此,演示完畢~~~
總結
以上是生活随笔為你收集整理的使用OAuth2实现认证服务器和资源服务器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 详细描述三个适于瀑布模型的项目_IT项目
- 下一篇: PostGIS安装教程