Spring boot --- Spring Oauth(一)

   文章部分圖片來自參考資料,這篇文章主要講 spring security  oauth html

概述

         上一篇咱們學習了 SS 中重要的工做原理和幾個大概的認證和受權過程。而 spring security oauth 用到的就是 spring security 知識,咱們學習 sso 以前先看一下oauth 是什麼,能夠學習阮一峯老師的文章java

         oauth 的流程圖以下 : (緊緊記住這張圖)git

bg2014051203

       主要的角色有資源持有者,資源服務器,認證服務器,還有用戶github

       受權(獲取 Access Token)的方式有多種方式web

  • 受權碼
  • 簡化模式
  • 客戶端模式
  • 密碼模式

     oauth 能夠理解成工做中,你(Client)去出差,回來須要報銷,會計(Authorzation Server)首先須要你請示老闆(Resource Owned)是否贊成給你報銷出差費用,假如贊成了,你就回來找會計,把老闆的憑證給她,她會給你一個token (獲取token過程的方式有多種,就是前面提到的), 而後你帶着 token 再去財務(Resource Server)領錢 ,結束流程。redis

 

Spring Security Oauth

    學習 Spring Security Oauth  ,先學習一個例子(出處),而後根據例子配合oauth 流程學習spring

         咱們按照上面的例子敲完代碼後,整個流程走完再結合oauth 受權的流程sql

    例子中使用的受權碼,而獲取Access Token ,爲什麼先給受權碼,而不直接給 Access Token 呢 ?數據庫

    給受權碼,再用受權碼去獲取Access Token 的緣由是受權碼可讓服務端知道client 的身份。安全

spring security oauth 角色

         oauth 獲取中幾個重要的角色中在 spring security oauth 中對應的有 :

  • @EnableResourceServer : 做爲資源服務器
  • @EnableAuthorazaitonServer : 做爲認證中心
  • @EnableOauthClient :作用被認證的客戶端,例如提供某個方式去認證 Github 或是 Facebook 的應用

          Resource Owned 的角色放在 Authorazation Server,就是代碼中的 UserDetail 。上面三個註解常常會混淆,咱們須要記住它們到底實現的用途是什麼,還有另一個註解 : @EnableSSO

UserDetail

         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());
    }

} 

 

TokenStore

          獲取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 {
   .... 

}

 

clientDetail

         使用受權碼方式獲取 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 基類表述

         能夠參考此處構建本身的 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 框架中的邏輯對應,更好地自定義改造。結合下面的參考資料,能夠完成單點登陸的功能。

參考資料

相關文章
相關標籤/搜索