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 框架中的逻辑对应,更好地自定义改造。结合下面的参考资料,能够完成单点登陆的功能。

参考资料

相关文章
相关标签/搜索