SpringSecurity + Oauth2.0搭建授權服務中心

       採用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

如有不對的理解,請指正,謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章