zoukankan      html  css  js  c++  java
  • 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 框架中的逻辑对应,更好地自定义改造。结合下面的参考资料,可以完成单点登录的功能。

    参考资料

  • 相关阅读:
    MongDB简单介绍
    Docker的简单介绍
    maven简单介绍
    粗谈Springboot框架,众所周知Springboot是有spring推出的微服务框架,什么是微服务框架呢!
    Springboot打包问题,打包的话是通过
    SpringBoot注解及swagger注解使用及规范
    properties配置
    日志配置
    c++几个面试题
    c++四种强制类型转化的区别
  • 原文地址:https://www.cnblogs.com/Benjious/p/10638913.html
Copyright © 2011-2022 走看看