採用oauth2.0 + SpringSecurity 搭建一個oauth2.0授權服務中心和一些資源服務器(密碼模式測試可用),實現簡單的微服務接口安全和權限控制。
Spring-Security存在於各個微服務系統中實現對url的訪問控制,oauth2實現對微服務所有的rest接口的權限控制。
這是一個資源服務器和授權中心分離的demo,token存到表中保存。
0 軟件環境
1、jdk1.8
2、springboot 2.0.7.release
3、spring-security 5
4、spring-security-oauth2 2.0.7.RELEASE
5、jpa
1 需要了解的概念
1、client:客戶端
2、resource owner : 用戶(資源擁有者)
3、authorization center:授權中心服務器(獲取token、驗證token)
4、resource server : 資源服務器
2 授權服務端搭建
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.0.7.RELEASE</version>
</dependency>
SecurityConfig類:security的web安全適配器
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//security配置: basic認證
http.httpBasic()
.and()
.authorizeRequests()
.anyRequest()
.authenticated();
}
/**
* 註冊一種密碼加密的bean,這裏可以用自己的實現
* 不註冊security報錯
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 註冊認證管理
* springboot2.0以後需要註冊,不註冊報錯
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
OauthServerConfig類:oauth一些自定義細節的配置,雖然有默認的配置,但是默認的並不能滿足我們大多數業務的需求。
@Configuration
@EnableAuthorizationServer
public class OauthServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private DataSource dataSource;
@Bean // 聲明TokenStore實現
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
/**
* token端點配置
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager).userDetailsService(userDetailsService)
.tokenStore(tokenStore());
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(endpoints.getTokenStore());
tokenServices.setSupportRefreshToken(true);
tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
tokenServices.setAccessTokenValiditySeconds( (int) TimeUnit.DAYS.toSeconds(1)); // 1天
endpoints.tokenServices(tokenServices);
}
/**
* 客戶端細節配置
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
/**
* oauth的一些權限控制
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
//允許表單認證
oauthServer.allowFormAuthenticationForClients();
//允許check_token訪問
oauthServer.checkTokenAccess("permitAll()");
}
}
實體類:OauthAccessToken、OauthClientDetails、OauthRefreshToken,這三個實體類是建表用的(JPA規範),你也可以自己寫sql建表,建完表後,要寫一個clientId和clientSecret,用於你自己測試用~~
@Getter
@Setter
@Entity
public class OauthAccessToken {
@Column(length=256)
private String tokenId;
@Lob
@Basic(fetch = FetchType.LAZY)
@Column(name = "token", columnDefinition = "BLOB",nullable=true)
private String token;
@Id
@Column(length=250)
private String authenticationId;
@Column(length=256)
private String userName;
@Column(length=256)
private String clientId;
@Lob
@Basic(fetch = FetchType.LAZY)
@Column(name = "authentication", columnDefinition = "BLOB",nullable=true)
private String authentication;
@Column(length=256)
private String refreshToken;
}
@Getter
@Setter
@Entity
public class OauthClientDetails {
@Id
@Column(length=250)
private String clientId;
@Column(length=256)
private String resourceIds;
@Column(length=256)
private String clientSecret;
@Column(length=256)
private String scope;
@Column(length=256)
private String authorizedGrantTypes;
@Column(length=256)
private String webServerRedirectUri;
@Column(length=256)
private String authorities;
@Column(length=11)
private Integer accessTokenValidity;
@Column(length=11)
private Integer refreshTokenValidity;
@Column(length=4096)
private String additionalInformation;
@Column(length=256)
private String autoapprove;
}
@Getter
@Setter
@Entity
public class OauthRefreshToken {
@Id
@Column(length=250)
private String tokenId;
@Lob
@Basic(fetch = FetchType.LAZY)
@Column(name = "token", columnDefinition = "BLOB",nullable=true)
private String token;
@Lob
@Basic(fetch = FetchType.LAZY)
@Column(name = "authentication", columnDefinition = "BLOB",nullable=true)
private String authentication;
}
注意:client_secret也要加密保存,加密的方式就是你在security裏配置的加密方式,明文的話,security會報錯!!!
MyUserDetail類:該自定義類實現了Security的UserDetailsService接口,這個接口裏有一堆關於用戶可用性規則的方法。
public class MyUserDetail implements UserDetails {
//我們自定義的用戶實體
private User user;
public MyUserDetail (User user){
this.user = user;
}
private static final long serialVersionUID = -5732157426896885480L;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
//暫時寫死admin權限,可拿user的權限加進去
return AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_admin");
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getAccount();
}
@Override
public boolean isAccountNonExpired() {
// 根據方法名字想這個方法是幹嘛的,true就是放行,可自定義去實現
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
MyUserDetailService類:該自定義類實現了security的UserDetailsService接口,裏面就一個方法loadUserByUsername,就是說請求過來了,我們可以獲取到請求中攜帶的用戶名,然後我們重寫成我們自己的驗證即可(這一步也沒必要,因爲我們實現了UserDetail接口,把用戶的一些信息塞到MyUserDetail的方法中,security就幫我做驗證了)。
@Component
public class MyUserDetailService implements UserDetailsService{
/**
* 自己寫的user服務層
*/
@Autowired
private UserService userServcie;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//根據用戶名去數據表中獲取user
User user = userServcie.getUserByAccount(username);
if(user == null){
throw new UsernameNotFoundException("");
}
return new MyUserDetail(user);
}
}
3 資源服務器端搭建
在另一個項目同樣引入授權服務的pom相關。
加入OauthResourceServerConfig類:聲明自己是一個資源服務器,並且所有請求都需要驗證
@Configuration
@EnableResourceServer
public class OauthResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/**")
.authenticated();
}
}
在application.properties中加入配置:
#授權服務中心的ip和端口
security.oauth2.resource.token-info-uri=http://localhost:port/oauth/check_token
#clientId(表裏存的)
security.oauth2.client.client-id=admin
#clinetSecret(表裏存的)
security.oauth2.client.client-secret=admin
4 總結
項目要用oauth2.0+spring-security來管理接口的安全性和權限,組裏也沒人會,於是惡補了幾天的知識。幾點建議:
(1)學習oauth2.0前,一定要先學習一下spring-security的知識。
(2)瞭解oauth2.0 授權中心三個重要的配置:configure(ClientDetailsServiceConfigurer clients)、configure(AuthorizationServerSecurityConfigurer oauthServer)、configure(AuthorizationServerEndpointsConfigurer endpoints)
(3)security的兩個接口的理解:UserDetails、UserDetailsService
(4)資源服務器如何和授權中心分離的,採用什麼方式去驗證token
如有不對的理解,請指正,謝謝!