zoukankan      html  css  js  c++  java
  • OAuth + Security

    PS:此文章为系列文章,建议从第一篇开始阅读。

    配置

    基础包依赖

    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    ================================== 在spring-boot中 ==================================
    <dependency>
       <groupId>org.springframework.security.oauth</groupId>
       <artifactId>spring-security-oauth2</artifactId>
       <version>2.3.5.RELEASE</version>
    </dependency>
    ================================== 或者在spring-cloud中 ==================================
    <dependency>
       <groupId>org.springframework.cloud</groupId>
       <artifactId>spring-cloud-starter-oauth2</artifactId>
    </dependency>
    

    配置三大核心

    认证服务器的配置需要继承 AuthorizationServerConfigurerAdapter 类,然后重写内部的方法来获得自己逻辑的token,其源码如下:

    public class AuthorizationServerConfigurerAdapter implements AuthorizationServerConfigurer {
    
        // 配置安全约束,主要是对默认的6个端点的开启与关闭配置
    	@Override
    	public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
    	}
    
        // 客户端信息配置
    	@Override
    	public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    	}
    
        // 认证服务器令牌访问端点配置和令牌服务配置,可以替换默认的端点url,配置支持的授权模式
    	@Override
    	public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    	}
    
    }
    
    1. 配置客户端详细信息

    ClientDetailsServiceConfigurer能够使用内存或者JDBC来实现客户端详情服务
    (ClientDetailsService ) , ClientDetailsService负责查找ClientDetails ,而ClientDetails有几个重要的属性如下列表:

    • clientld: (必须的)用来标识客户的Id。
    • secret : ( 需要值得信任的客户端)客户端安全码,如果有的话。
    • scope :用来限制客户端的访问范围,如果为空(默认)的话,那么客户端拥有全部的访问范围。
    • authorizedGrantTypes :此客户端可以使用的授权类型,默认为空。
    • authorities :此客户端可以使用的权限(基于Spring Security authorities )。

    客户端详情( ClientDetails )能够在应用程序运行的时候进行更新,可以通过访问底层的存储服务(例如
    将客户端详情存储在一个关系数据库的表中,就可以使用JdbcClientDetailsService或者通过自己实现
    ClientRegistrationService接口(同时你也可以实现ClientDetailsService接口)来进行管理。

    1. 令牌访问端点和令牌服务配置

    AuthorizationServerEndpointsConfigurer这个对象的实例可以完成令牌服务以及令牌endpoint配置。

    配置授权类型( Grant Types )

    AuthorizationServerEndpointsConfigurer通过设定以下属性决定支持的授权类型( Grant Types ) :

    • authenticationManager:认证管理器,当你选择了资源所有者密码(password)授权类型的时候,请设置这个属性注入一个AuthenticationManager对象。
    • userDetailsService :如果你设置了这个属性的话,那说明你有一个自己的UserDetailsService接口的实现,或者你可以把这个东西设置到全局域上面去(例如GlobalAuthenticationManagerConfigurer这个配置对象),当你设置了这个之后,那么"refresh_token"即刷新令牌授权类型模式的流程中就会包含一个检查,用来确保这个账号是否仍然有效,假如说你禁用了这个账户的话。
    • authorizationCodeServices:这个属性是用来设置授权码服务的(即AuthorizationCodeServices的实例对象) ,主要用于"authorization_ code" 授权码类型模式。
    • implicitGrantService :这个属性用于设置隐式授权模式,用来管理隐式授权模式的状态。
    • tokenGranter :当你设置了这个东西(即TokenGranter接口实现),那么授权将会交由你来完全掌控,并且会忽略掉上面的这几个属性,这个属性一般是用作拓展用途的,即标准的四种授权模式已经满足不了你的需求的时候,才会考虑使用这个。
    配置授权端点的URL

    默认的端点URL有以下6个:

    • /oauth/authorize : 授权端点。
    • /oauth/token : 令牌端点。
    • /oauth/confirm _access : 用户确认授权提交端点。
    • /oauth/error : 授权服务错误信息端点。
    • /oauth/check_token : 用于资源服务访问的令牌解析端点。
    • /oauth/token_key : 提供公有密匙的端点,如果你使用JWT令牌的话。

    这些端点都可以通过配置的方式更改其路径,在AuthorizationServerEndpointsConfigurer中有一个叫pathMapping()的方法用来配置端点URL链接,他有两个参数:

    • 第一个参数:string类型,默认的URL
    • 第二个参数:string类型,进行替代的URL

    以上的参数都是以"/"开始的字符串

    1. 配置令牌端点的安全约束

    AuthorizationServerSecurityConfigurer : 用来配置令牌端点(Token Endpoint)的安全约束,如配置资源服务器需要验证token的/oauth/check_token

    总结

    授权服务配置分成三大块,可以关联记忆。

    既然要完成认证,它首先得知道客户端信息从哪儿读取,因此要进行客户端详情配置。

    既然要颁发token,那必须得定义token的相关endpoint,以及token如何存取,以及客户端支持哪些类型的token。

    既然暴露除了一些endpoint,那对这些endpoint可以定义一些安全上的约束等。

    详细代码如下:

    • 认证服务器配置类
    /**
     * @author zhongyj <1126834403@qq.com><br/>
     * @date 2020/6/1
     */
    @Configuration
    @EnableAuthorizationServer
    public class DimplesAuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
    
        private PasswordEncoder passwordEncoder;
    
        private TokenStore tokenStore;
    
        private ClientDetailsService clientDetailsService;
    
        private AuthenticationManager authenticationManager;
    
        @Autowired
        public DimplesAuthorizationServerConfiguration(PasswordEncoder passwordEncoder, TokenStore tokenStore, ClientDetailsService clientDetailsService, AuthenticationManager authenticationManager) {
            this.passwordEncoder = passwordEncoder;
            this.tokenStore = tokenStore;
            this.clientDetailsService = clientDetailsService;
            this.authenticationManager = authenticationManager;
        }
    
        /**
         * 令牌端点的安全约束
         *
         * @param security AuthorizationServerSecurityConfigurer
         */
        @Override
        public void configure(AuthorizationServerSecurityConfigurer security) {
            security
                    // /oauth/token_key公开
                    .tokenKeyAccess("permitAll()")
                    // /oauth/check_token公开
                    .checkTokenAccess("permitAll()")
                    .allowFormAuthenticationForClients();
        }
    
        /**
         * 客户端详情服务,暂时配置在内存中,后期改为存在数据库
         *
         * @param clients ClientDetailsServiceConfigurer
         * @throws Exception Exception
         */
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            clients.inMemory()
                    .withClient("dimples")
                    .secret(this.passwordEncoder.encode("123456"))
                    // 为了测试,所以开启所有的方式,实际业务根据需要选择
                    .authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")
                    .accessTokenValiditySeconds(3600)
                    .refreshTokenValiditySeconds(864000)
                    .scopes("select")
                    // false跳转到授权页面,在授权码模式中会使用到
                    .autoApprove(false)
                    // 验证回调地址
                    .redirectUris("http://www.baidu.com");
        }
    
        /**
         * 令牌访问端点和令牌访问服务
         *
         * @param endpoints AuthorizationServerEndpointsConfigurer
         */
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
            endpoints
                    // 密码模式
                   .authenticationManager(authenticationManager)
                    // 授权码模式
                    .authorizationCodeServices(authorizationCodeServices())
                    // 令牌管理
                    .tokenServices(tokenServices());
    
        }
    
        /**
         * 令牌管理服务
         *
         * @return TokenServices
         */
        @Bean
        public AuthorizationServerTokenServices tokenServices() {
            DefaultTokenServices services = new DefaultTokenServices();
            // 客户端详情服务
            services.setClientDetailsService(clientDetailsService);
            // 支持令牌刷新
            services.setSupportRefreshToken(true);
            // 令牌存储策略
            services.setTokenStore(tokenStore);
            // 令牌默认有效期2小时
            services.setAccessTokenValiditySeconds(7200);
            // 刷新令牌默认有效期2天
            services.setRefreshTokenValiditySeconds(259200);
            return services;
        }
    
        /**
         * 设置授权码模式的授权码如何存储,暂时采用内存
         *
         * @return AuthorizationCodeServices
         */
        @Bean
        public AuthorizationCodeServices authorizationCodeServices() {
            return new InMemoryAuthorizationCodeServices();
        }
    
    }
    
    
    • Token存储配置类
    /**
     * @author zhongyj <1126834403@qq.com><br/>
     * @date 2020/6/1
     */
    @Configuration
    public class TokenConfigure {
    
        @Bean
        public TokenStore tokenStore() {
            return new InMemoryTokenStore();
        }
    
    }
    
    • Security 安全配置
    /**
     * @author zhongyj <1126834403@qq.com><br/>
     * @date 2020/6/1
     */
    @Configuration
    public class DimplesWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    
        private DimplesUserDetailServiceImpl userDetailsService;
    
        private PasswordEncoder passwordEncoder;
    
        @Autowired
        public DimplesWebSecurityConfiguration(DimplesUserDetailServiceImpl userDetailsService, PasswordEncoder passwordEncoder) {
            this.userDetailsService = userDetailsService;
            this.passwordEncoder = passwordEncoder;
        }
    
        /**
         * 安全拦截机制(最重要)
         */
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.csrf().disable()
                    .authorizeRequests()
                    .antMatchers("/oauth/**").permitAll()
                    .anyRequest().authenticated()
                    .and().formLogin();
        }
    
        /**
         * 非必须配置,可以不配
         * 认证管理配置
         * 连接数据查询用户信息,与数据库中密码比对
         *
         * @param auth AuthenticationManagerBuilder
         * @throws Exception Exception
         */
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
        }
    
    
        /**
         * 认证管理区配置,密码模式需要用到
         *
         * @return AuthenticationManager
         * @throws Exception Exception
         */
        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    
    }
    
    • 配置密码
    @Configuration
    public class DimplesConfigure {
    
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
    }
    
    • 用户信息获取类
    @Configuration
    public class DimplesUserDetailServiceImpl implements UserDetailsService {
    
        private PasswordEncoder passwordEncoder;
    
        @Autowired
        public DimplesUserDetailServiceImpl(PasswordEncoder passwordEncoder) {
            this.passwordEncoder = passwordEncoder;
        }
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            // 模拟一个用户,替代数据库获取逻辑
            DimplesUser user = new DimplesUser();
            user.setUserName(username);
            user.setPassword(this.passwordEncoder.encode("123456"));
            // 输出加密后的密码
            System.out.println(user.getPassword());
    
            return new User(username, user.getPassword(), user.isEnabled(),
                    user.isAccountNonExpired(), user.isCredentialsNonExpired(),
                    user.isAccountNonLocked(), AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
        }
    }
    
    @Data
    public class DimplesUser implements Serializable {
        private static final long serialVersionUID = 3497935890426858541L;
    
        private String userName;
    
        private String password;
    
        private boolean accountNonExpired = true;
    
        private boolean accountNonLocked= true;
    
        private boolean credentialsNonExpired= true;
    
        private boolean enabled= true;
    }
    

    可能出现的问题

    • 最可能出现的问题,项目启动时报循环依赖的错误,要注意代码中在当前配置的类中写的Bean不能再当前类中去使用@Autowired注入
    • 在登录时控制台报错堆栈溢出

    错误追踪:

    是由于配置密码模式下的AuthenticationManager时,方法名称为authenticationManager,更改为authenticationManagerBean即可

    测试

    授权码模式下获取token

    1. 首先获取授权码
      在浏览器输入 http://127.0.0.1:8080/oauth/authorize?client_id=dimples&response_type=code&redirect_uri=http://www.baidu.com

    跳转到登录界面,然后进行用户登录,登录成功以后选择用户授权,获取相应的授权码,将会显示在重定向URL的链接后面。如下图:

    1. 获取code

    image

    1. 验证用户

    image

    1. 授权

    image

    1. 得到code

    image

    1. 拿着这个获取的code的值去/oauth/token端点获取token,如下图:

    image

    这个code有错将不能获取到token,因为这个将会保存在程序内存中,同时这个code只能获取一次token

    密码模式

    /oauth/token?client_id=dimples&client_secret=123456&grant_type=password&username=dimples&password=123456

    这种模式十分简单,但是也意味着直接将用户的敏感信息泄露给了client端,因此这种模式一般只用于client是我们自己开发的情况下。

    此处的客户端书写有两种方式:

    1. 直接写在请求参数中

    image

    1. 写在请求头中

    image

    实际上,在请求头中传输时,其格式将会是,在请求头中添加 Authorization,其值是 Basic加空格client_id:client_secret就是在AuthorizationServerConfigure类configure(ClientDetailsServiceConfigurer clients)方法中定义的client和secret)经过base64加密后的值(http://tool.oschina.net/encrypt?type=3))

    image

    客户端模式

    /oauth/token?client_id=dimples&client_secret=123456&grant_type=client_credentials

    image

  • 相关阅读:
    百度离线地图
    lightdb for postgresql高可用之repmgr组件日常管理命令及注意实现
    LightDB13.3-21.2 Release Note
    postgresql的FRONTEND宏定义
    ppt设置自动循环播放
    url上的jsessionid问题及解决方法
    postgresql xact
    pg_control文件的作用
    Extension module_pathname and .sql.in
    psr/cache 通过composer 安装报错syntax error, unexpected '|', expecting variable (T_VARIABLE)
  • 原文地址:https://www.cnblogs.com/reroyalup/p/13030191.html
Copyright © 2011-2022 走看看