• Spring boot --- Spring Oauth(一)


       文章部分图片来自参考资料,这篇文章主要讲 spring security  oauth

    概述

             上一篇我们学习了 SS 中重要的工作原理和几个大概的认证和授权过程。而 spring security oauth 用到的就是 spring security 知识,我们学习 sso 之前先看一下oauth 是什么,可以学习阮一峰老师的文章

             oauth 的流程图如下 : (牢牢记住这张图)

    bg2014051203

           主要的角色有资源持有者,资源服务器,认证服务器,还有用户

           授权(获取 Access Token)的方式有多种方式

    • 授权码
    • 简化模式
    • 客户端模式
    • 密码模式

         oauth 可以理解成工作中,你(Client)去出差,回来需要报销,会计(Authorzation Server)首先需要你请示老板(Resource Owned)是否同意给你报销出差费用,假如同意了,你就回来找会计,把老板的凭证给她,她会给你一个token (获取token过程的方式有多种,就是前面提到的), 然后你带着 token 再去财务(Resource Server)领钱 ,结束流程。

    Spring Security Oauth

        学习 Spring Security Oauth  ,先学习一个例子(出处),然后根据例子配合oauth 流程学习

             我们按照上面的例子敲完代码后,整个流程走完再结合oauth 授权的流程

        例子中使用的授权码,而获取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 框架中的逻辑对应,更好地自定义改造。结合下面的参考资料,可以完成单点登录的功能。

    参考资料

  • 相关阅读:
    Oracle函数如何把符串装换为小写的格式
    Oralce中的synonym同义词
    JS中getYear()的兼容问题
    How to do SSH Tunneling (Port Forwarding)
    所谓深度链接(Deep linking)
    upload size of asp.net
    发一个自动刷网站PV流量的小工具
    解决Visual Studio 2008 下,打开.dbml(LINQ) 文件时,提示"The operation could not be completed." 的问题。
    在资源管理器中使鼠标右键增加一个命令,运行cmd,同时使得当前路径为资源管理器当前的目录
    使用SQL语句获取Sql Server数据库的版本
  • 原文地址:https://www.cnblogs.com/Benjious/p/10638913.html
Copyright © 2020-2023  润新知