美文网首页
2020-08-28 oauth2四种模式和springboot

2020-08-28 oauth2四种模式和springboot

作者: zero_93a5 | 来源:发表于2020-08-29 11:50 被阅读0次

    一、客户端模式

    1、pom.xml核心jar包:
     <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-oauth2</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
            </dependency>
    
    2、配置文件,基础端口,工程名,redis配置
    spring.application.name=serve-security
    server.port=8080
    # Redis数据库索引(默认为0)
    spring.redis.database=0
    # Redis服务器地址
    spring.redis.host=127.0.0.1
    # Redis服务器连接端口
    spring.redis.port=6379
    # Redis服务器连接密码(默认为空)
    spring.redis.password=
    # 连接池最大连接数(使用负值表示没有限制)
    spring.redis.pool.max-active=8
    # 连接池最大阻塞等待时间(使用负值表示没有限制)
    spring.redis.pool.max-wait=-1
    # 连接池中的最大空闲连接
    spring.redis.pool.max-idle=8
    # 连接池中的最小空闲连接
    spring.redis.pool.min-idle=0
    # 连接超时时间(毫秒)
    spring.redis.timeout=0
    
    3、AuthorizationServerConfig 认证服务器配置(spring security oauth2的http配置)
    package com.sun.securityserve.config;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.http.HttpMethod;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
    import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
    import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
    import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
    import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
    import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
    
    @Configuration
    @EnableAuthorizationServer
    public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    
        private static final String RESOURCE_IDS = "order";
    
        @Autowired
        AuthenticationManager authenticationManager;
    
        @Autowired
        RedisConnectionFactory redisConnectionFactory;
    
        @Autowired
        private UserDetailsService userDetailsService;
    
        /**authorizedGrantTypes:
         *         1.授权码模式(authorization code)
         *         2.简化模式(implicit)
         *         3.密码模式(resource owner password credentials)
         *         4.客户端模式(client credentials)
         * @param clients
         * @throws Exception
         */
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    
            String finalSecret = "{bcrypt}" + new BCryptPasswordEncoder().encode("123456");
            //配置一个用于client认证
            clients.inMemory()
    
                    //client模式
                    .withClient("client_1")
                    .resourceIds(RESOURCE_IDS)
                    .authorizedGrantTypes("client_credentials", "refresh_token")
                    .scopes("select")
                    .authorities("oauth2")
                    .secret(finalSecret);
        }
    
        /**
         * 认证服务端点配置
         */
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
            endpoints
                    //用户管理
                    .userDetailsService(userDetailsService)
                    //token存到redis
                    .tokenStore(new RedisTokenStore(redisConnectionFactory))
                    //启用oauth2管理
                    .authenticationManager(authenticationManager)
                    //接收GET和POST
                    .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
        }
    
        /**
         * 用来配置令牌端点(Token Endpoint)的安全约束
         * @param oauthServer
         */
        @Override
        public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
            oauthServer.allowFormAuthenticationForClients();
        }
    }
    
    4、RedisConfig工具类
    package com.sun.securityserve.config;
    
    import org.springframework.cache.annotation.CachingConfigurerSupport;
    import org.springframework.cache.annotation.EnableCaching;
    import org.springframework.cache.interceptor.KeyGenerator;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
    import org.springframework.util.StringUtils;
    
    import java.lang.reflect.Method;
    
    @Configuration
    @EnableCaching
    public class RedisConfig extends CachingConfigurerSupport {
    
        /**
         * redisTemplate 序列化使用的jdkSerializeable, 存储二进制字节码, 所以自定义序列化类,方便调试redis
         *
         * @param redisConnectionFactory
         * @return
         */
        @Bean
        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
    
            RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
    
            //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
            redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
            redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
    
            //使用StringRedisSerializer来序列化和反序列化redis的key
            redisTemplate.setKeySerializer(new StringRedisSerializer());
            redisTemplate.setHashKeySerializer(new StringRedisSerializer());
    
            //开启事务
            redisTemplate.setEnableTransactionSupport(true);
    
            redisTemplate.setConnectionFactory(redisConnectionFactory);
    
            return redisTemplate;
        }
    
        /**
         * 自定义生成key的策略
         *
         * @return
         */
        @Bean
        @Override
        public KeyGenerator keyGenerator() {
            return new KeyGenerator() {
                @Override
                public Object generate(Object target, Method method, Object... params) {
                    return target.getClass().getSimpleName() + "_"
                            + method.getName() + "_"
                            + StringUtils.arrayToDelimitedString(params, "_");
                }
            };
        }
    }
    
    5、WebSecurityConfig 拦截配置(spring security的http配置)
    package com.sun.securityserve.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.core.userdetails.User;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.factory.PasswordEncoderFactories;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.security.provisioning.InMemoryUserDetailsManager;
    
    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    
        /**
         * 自定义承接AuthorizationServerConfig 中的userDetailsService,否则会报错
         * @return
         */
        @Bean
        @Override
        protected UserDetailsService userDetailsService() {
            BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
    
            String finalPassword = "{bcrypt}" + bCryptPasswordEncoder.encode("123456");
            InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
            manager.createUser(User.withUsername("user").password(finalPassword).authorities("USER").build());
            
     manager.createUser(User.withUsername("admin").password(finalPassword).authorities("USER").build());
    
            return manager;
        }
    
        @Bean
        PasswordEncoder passwordEncoder() {
            return PasswordEncoderFactories.createDelegatingPasswordEncoder();
        }
    
        /**
         * 注入AuthenticationManager接口,启用OAuth2密码模式
         *
         * @return
         * @throws Exception
         */
        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            AuthenticationManager manager = super.authenticationManagerBean();
            return manager;
        }
    
        /**
         * 通过HttpSecurity实现Security的自定义过滤配置
         *
         * @param httpSecurity
         * @throws Exception
         */
        @Override
        protected void configure(HttpSecurity httpSecurity) throws Exception {
            httpSecurity
                    .requestMatchers().anyRequest()
                    .and()
                    .authorizeRequests()
                    .antMatchers("/oauth/**").permitAll()
                    .and()
                    .authorizeRequests()
                    .antMatchers("/security/**").authenticated();
        }
    }
    

    测试:
    http://localhost:8080/oauth/token?grant_type=client_credentials&scope=select&client_id=client_1&client_secret=123456

    grant_type:固定为client_credentials
    scope:对应的AuthorizationServerConfig.configure(ClientDetailsServiceConfigurer clients)中配置的scopes中的参数
    client_id:模块名称,withClient对应的值
    client_secret:密码,secret加密之前的参数

    返回:

    {
        "access_token": "71d384ed-24c1-4945-be58-aa059e47bbda",
        "token_type": "bearer",
        "expires_in": 40356,
        "scope": "select"
    }
    

    二、密码模式

    代码在上面的AuthorizationServerConfig.java代码中方法为configure(ClientDetailsServiceConfigurer clients)
    里面

     clients.inMemory()
                    //client模式
                    .withClient("client_1")
                    .resourceIds(RESOURCE_IDS)
                    .authorizedGrantTypes("client_credentials", "refresh_token")
                    .scopes("select")
                    .authorities("oauth2")
                    .secret(finalSecret)
                    .and()
                    //添加密码模式
                    .withClient("client_2")
                    .resourceIds(RESOURCE_IDS)
                    .authorizedGrantTypes("password", "refresh_token")
                    .scopes("select")
                    .authorities("oauth2")
                    .secret(finalSecret);
    

    测试:
    http://localhost:8080/oauth/token?grant_type=password&scope=select&username=admin&password=123456&client_id=client_2&client_secret=123456
    返回:

    {
        "access_token": "304de3f4-4ba3-475c-84dc-6efd8369becf",
        "token_type": "bearer",
        "refresh_token": "a093e2f1-a4ae-436c-a8b6-bab79869e283",
        "expires_in": 43199,
        "scope": "select"
    }
    

    三、授权码模式

    1、代码在上面的AuthorizationServerConfig.java代码中方法为configure(ClientDetailsServiceConfigurer clients)
    里面:

     clients.inMemory()
    //授权码模式
                    .withClient("client_code")
                    .secret(finalSecret)
                    .authorizedGrantTypes("authorization_code", "refresh_token")
                    .scopes("all")
                    .redirectUris("http://localhost:8080/security/login")
                    .accessTokenValiditySeconds(1200)
                    .refreshTokenValiditySeconds(50000);
    

    2、修改WebSecurityConfig.java中的configure(HttpSecurity httpSecurity) 方法
    改为:

         httpSecurity.httpBasic().and().authorizeRequests()
                    .antMatchers("/js/**", "/css/**", "/images/**","/oauth/**")
                    .permitAll()
                    .anyRequest()
                    .permitAll()
                    .and().csrf().disable();
    

    3、启动测试:

    浏览器测试:
    http://localhost:8080/oauth/authorize?response_type=code&redirect_uri=http://localhost:8080/security/login&client_id=client_code&scope=all&grant_type=authorization_code&client_secret=123456

    image.png

    response_type固定为:code

    会跳转到登录页面,输入上面设置的用户:user 密码:123456 就会到授权码页面。


    image.png

    选择approve,就会跳转到对应的页面,同时url后添加code=44MlYe。

    4、常见问题:

    a)User must be authenticated with Spring Security before authorization can be completed.
    网上很多人认为这个报错是加载问题,大部分是设置:security.oauth2.resource.filter-order = 3或者认为是版本问题。
    其实除了这个设置方式意外就是注意WebSecurityConfig.configure(HttpSecurity httpSecurity)这个方法放开/oauth/**。
    
    b) 其他异常情况,大部分是参数传参问题,注意下代码和上面请求的参数,另外注意跳转的uri的路径,避免跨域问题
    

    四、简化模式

    整体流程通授权码模式

    AuthorizationServerConfig.configure(ClientDetailsServiceConfigurer clients) 中代码authorizedGrantTypes设置为 .authorizedGrantTypes("implicit")

    测试
    http://localhost:8080/oauth/authorize?response_type=token&redirect_uri=http://localhost:8080/security/login&client_id=client_code&scope=all&grant_type=implicit&client_secret=123456

    access_token:表示访问令牌,必选项。
    token_type:表示令牌类型,该值大小写不敏感,必选项。
    expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。
    scope:表示权限范围,如果与客户端申请的范围一致,此项可省略。
    state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。
    response_type:固定token

    相关文章

      网友评论

          本文标题:2020-08-28 oauth2四种模式和springboot

          本文链接:https://www.haomeiwen.com/subject/sgtlsktx.html