文章部分圖片來自參考資料,這篇文章主要講 spring security oauth html
上一篇咱們學習了 SS 中重要的工做原理和幾個大概的認證和受權過程。而 spring security oauth 用到的就是 spring security 知識,咱們學習 sso 以前先看一下oauth 是什麼,能夠學習阮一峯老師的文章java
oauth 的流程圖以下 : (緊緊記住這張圖)git
主要的角色有資源持有者,資源服務器,認證服務器,還有用戶github
受權(獲取 Access Token)的方式有多種方式web
oauth 能夠理解成工做中,你(Client)去出差,回來須要報銷,會計(Authorzation Server)首先須要你請示老闆(Resource Owned)是否贊成給你報銷出差費用,假如贊成了,你就回來找會計,把老闆的憑證給她,她會給你一個token (獲取token過程的方式有多種,就是前面提到的), 而後你帶着 token 再去財務(Resource Server)領錢 ,結束流程。redis
學習 Spring Security Oauth ,先學習一個例子(出處),而後根據例子配合oauth 流程學習spring
咱們按照上面的例子敲完代碼後,整個流程走完再結合oauth 受權的流程sql
例子中使用的受權碼,而獲取Access Token ,爲什麼先給受權碼,而不直接給 Access Token 呢 ?數據庫
給受權碼,再用受權碼去獲取Access Token 的緣由是受權碼可讓服務端知道client 的身份。安全
oauth 獲取中幾個重要的角色中在 spring security oauth 中對應的有 :
Resource Owned 的角色放在 Authorazation Server,就是代碼中的 UserDetail 。上面三個註解常常會混淆,咱們須要記住它們到底實現的用途是什麼,還有另一個註解 : @EnableSSO
UserDetail 的做用是用來認證便是上面oauth 流程圖的A 步驟,示例以下 :
@Service public class MyUserDetailsService implements UserDetailsService { @Autowired private UserService userService; /** * 受權的時候是對角色受權,而認證的時候應該基於資源,而不是角色,由於資源是不變的,而用戶的角色是會變的 */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { SysUser sysUser = userService.getUserByName(username); if (null == sysUser) { throw new UsernameNotFoundException(username); } List<SimpleGrantedAuthority> authorities = new ArrayList<>(); for (SysRole role : sysUser.getRoleList()) { for (SysPermission permission : role.getPermissionList()) { authorities.add(new SimpleGrantedAuthority(permission.getCode())); } } return new User(sysUser.getUsername(), sysUser.getPassword(), authorities); } } @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { ... @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(myUserDetailsService).passwordEncoder(passwordEncoder()); } }
獲取token,那麼很明顯須要一個儲存token 的容器,例如咱們想使用 Redis 來存儲token ,當咱們須要實現本身token 存儲容器時能夠以下使用 :
@Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { ... /** * 該方法是用來配置Authorization Server endpoints的一些非安全特性的,好比token存儲、token自定義、受權類型等等的 * 默認狀況下,你不須要作任何事情,除非你須要密碼受權,那麼在這種狀況下你須要提供一個AuthenticationManager */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authenticationManager(authenticationManager) .tokenStore(new MyRedisTokenStore(redisConnectionFactory)); } } @Service public class MyRedisTokenStore implements TokenStore { .... }
使用受權碼方式獲取 Access Token 時先發放受權碼,而是否能夠發送受權碼須要驗證client 的身份,clientDetail 即是即是表述 client 基本信息的類
@Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { ... @Resource private DataSource dataSource; /** * 配置ClientDetailsService * 注意,除非你在下面的configure(AuthorizationServerEndpointsConfigurer)中指定了一個AuthenticationManager,不然密碼受權方式不可用。 * 至少配置一個client,不然服務器將不會啓動。 */ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.jdbc(dataSource); } }
能夠參考此處構建本身的 token 和 clientDetail ,如下是數據庫實現
-- used in tests that use HSQL create table oauth_client_details ( client_id VARCHAR(256) PRIMARY KEY, resource_ids VARCHAR(256), client_secret VARCHAR(256), scope VARCHAR(256), authorized_grant_types VARCHAR(256), web_server_redirect_uri VARCHAR(256), authorities VARCHAR(256), access_token_validity INTEGER, refresh_token_validity INTEGER, additional_information VARCHAR(4096), autoapprove VARCHAR(256) ); create table oauth_client_token ( token_id VARCHAR(256), token LONGVARBINARY, authentication_id VARCHAR(256) PRIMARY KEY, user_name VARCHAR(256), client_id VARCHAR(256) ); create table oauth_access_token ( token_id VARCHAR(256), token LONGVARBINARY, authentication_id VARCHAR(256) PRIMARY KEY, user_name VARCHAR(256), client_id VARCHAR(256), authentication LONGVARBINARY, refresh_token VARCHAR(256) ); create table oauth_refresh_token ( token_id VARCHAR(256), token LONGVARBINARY, authentication LONGVARBINARY ); create table oauth_code ( code VARCHAR(256), authentication LONGVARBINARY ); create table oauth_approvals ( userId VARCHAR(256), clientId VARCHAR(256), scope VARCHAR(256), status VARCHAR(10), expiresAt TIMESTAMP, lastModifiedAt TIMESTAMP ); -- customized oauth_client_details table create table ClientDetails ( appId VARCHAR(256) PRIMARY KEY, resourceIds VARCHAR(256), appSecret VARCHAR(256), scope VARCHAR(256), grantTypes VARCHAR(256), redirectUrl VARCHAR(256), authorities VARCHAR(256), access_token_validity INTEGER, refresh_token_validity INTEGER, additionalInformation VARCHAR(4096), autoApproveScopes VARCHAR(256) );
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private MyUserDetailsService myUserDetailsService; @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/oauth/**","/login/**", "/logout").permitAll() .anyRequest().authenticated() // 其餘地址的訪問均需驗證權限 .and() .formLogin() .loginPage("/login") .and() .logout().logoutUrl("/logout").logoutSuccessUrl("/login"); } @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/assets/**"); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(myUserDetailsService).passwordEncoder(passwordEncoder()); } @Bean @Override public AuthenticationManager authenticationManager() throws Exception { return super.authenticationManager(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
@Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Resource private DataSource dataSource; @Autowired RedisConnectionFactory redisConnectionFactory; @Autowired private AuthenticationManager authenticationManager; /** * 配置受權服務器的安全,意味着其實是/oauth/token端點。 * /oauth/authorize端點也應該是安全的 * 默認的設置覆蓋到了絕大多數需求,因此通常狀況下你不須要作任何事情。 */ @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { super.configure(security); } /** * 配置ClientDetailsService * 注意,除非你在下面的configure(AuthorizationServerEndpointsConfigurer)中指定了一個AuthenticationManager,不然密碼受權方式不可用。 * 至少配置一個client,不然服務器將不會啓動。 */ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.jdbc(dataSource); } /** * 該方法是用來配置Authorization Server endpoints的一些非安全特性的,好比token存儲、token自定義、受權類型等等的 * 默認狀況下,你不須要作任何事情,除非你須要密碼受權,那麼在這種狀況下你須要提供一個AuthenticationManager */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authenticationManager(authenticationManager) .tokenStore(new MyRedisTokenStore(redisConnectionFactory)); } }
當資源服務器和認證服務器是同一個服務器的時候 :
Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private MyUserDetailsService myUserDetailsService; @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/oauth/**","/login/**", "/logout").permitAll() .anyRequest().authenticated() // 其餘地址的訪問均需驗證權限 .and() .formLogin() .loginPage("/login") .and() .logout().logoutUrl("/logout").logoutSuccessUrl("/login"); } @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/assets/**"); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(myUserDetailsService).passwordEncoder(passwordEncoder()); } @Bean @Override public AuthenticationManager authenticationManager() throws Exception { return super.authenticationManager(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
http://blog.didispace.com/xjf-spring-security-1/
http://blog.didispace.com/xjf-spring-security-1/
文章並非 spring oauth 的入門篇,主要是結合 oauth 的流程圖找到對應 spring security oauth 框架中的邏輯對應,更好地自定義改造。結合下面的參考資料,能夠完成單點登陸的功能。