2020-08-28 oauth2四種模式和springboot結(jié)合使用

一、客戶(hù)端模式

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、配置文件,基礎(chǔ)端口,工程名,redis配置
spring.application.name=serve-security
server.port=8080
# Redis數(shù)據(jù)庫(kù)索引(默認(rèn)為0)
spring.redis.database=0
# Redis服務(wù)器地址
spring.redis.host=127.0.0.1
# Redis服務(wù)器連接端口
spring.redis.port=6379
# Redis服務(wù)器連接密碼(默認(rèn)為空)
spring.redis.password=
# 連接池最大連接數(shù)(使用負(fù)值表示沒(méi)有限制)
spring.redis.pool.max-active=8
# 連接池最大阻塞等待時(shí)間(使用負(fù)值表示沒(méi)有限制)
spring.redis.pool.max-wait=-1
# 連接池中的最大空閑連接
spring.redis.pool.max-idle=8
# 連接池中的最小空閑連接
spring.redis.pool.min-idle=0
# 連接超時(shí)時(shí)間(毫秒)
spring.redis.timeout=0
3、AuthorizationServerConfig 認(rèn)證服務(wù)器配置(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.授權(quán)碼模式(authorization code)
     *         2.簡(jiǎn)化模式(implicit)
     *         3.密碼模式(resource owner password credentials)
     *         4.客戶(hù)端模式(client credentials)
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

        String finalSecret = "{bcrypt}" + new BCryptPasswordEncoder().encode("123456");
        //配置一個(gè)用于client認(rèn)證
        clients.inMemory()

                //client模式
                .withClient("client_1")
                .resourceIds(RESOURCE_IDS)
                .authorizedGrantTypes("client_credentials", "refresh_token")
                .scopes("select")
                .authorities("oauth2")
                .secret(finalSecret);
    }

    /**
     * 認(rèn)證服務(wù)端點(diǎn)配置
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints
                //用戶(hù)管理
                .userDetailsService(userDetailsService)
                //token存到redis
                .tokenStore(new RedisTokenStore(redisConnectionFactory))
                //啟用oauth2管理
                .authenticationManager(authenticationManager)
                //接收GET和POST
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
    }

    /**
     * 用來(lái)配置令牌端點(diǎn)(Token Endpoint)的安全約束
     * @param oauthServer
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
        oauthServer.allowFormAuthenticationForClients();
    }
}
4、RedisConfig工具類(lèi)
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, 存儲(chǔ)二進(jìn)制字節(jié)碼, 所以自定義序列化類(lèi),方便調(diào)試redis
     *
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {

        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();

        //使用Jackson2JsonRedisSerializer來(lái)序列化和反序列化redis的value值
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());

        //使用StringRedisSerializer來(lái)序列化和反序列化redis的key
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());

        //開(kāi)啟事務(wù)
        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,否則會(huì)報(bào)錯(cuò)
     * @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;
    }

    /**
     * 通過(guò)HttpSecurity實(shí)現(xiàn)Security的自定義過(guò)濾配置
     *
     * @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();
    }
}

測(cè)試:
http://localhost:8080/oauth/token?grant_type=client_credentials&scope=select&client_id=client_1&client_secret=123456

grant_type:固定為client_credentials
scope:對(duì)應(yīng)的AuthorizationServerConfig.configure(ClientDetailsServiceConfigurer clients)中配置的scopes中的參數(shù)
client_id:模塊名稱(chēng),withClient對(duì)應(yīng)的值
client_secret:密碼,secret加密之前的參數(shù)

返回:

{
    "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);

測(cè)試:
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"
}

三、授權(quán)碼模式

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

 clients.inMemory()
//授權(quán)碼模式
                .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、啟動(dòng)測(cè)試:

瀏覽器測(cè)試:
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

會(huì)跳轉(zhuǎn)到登錄頁(yè)面,輸入上面設(shè)置的用戶(hù):user 密碼:123456 就會(huì)到授權(quán)碼頁(yè)面。


image.png

選擇approve,就會(huì)跳轉(zhuǎn)到對(duì)應(yīng)的頁(yè)面,同時(shí)url后添加code=44MlYe。

4、常見(jiàn)問(wèn)題:

a)User must be authenticated with Spring Security before authorization can be completed.
網(wǎng)上很多人認(rèn)為這個(gè)報(bào)錯(cuò)是加載問(wèn)題,大部分是設(shè)置:security.oauth2.resource.filter-order = 3或者認(rèn)為是版本問(wèn)題。
其實(shí)除了這個(gè)設(shè)置方式意外就是注意WebSecurityConfig.configure(HttpSecurity httpSecurity)這個(gè)方法放開(kāi)/oauth/**。
b) 其他異常情況,大部分是參數(shù)傳參問(wèn)題,注意下代碼和上面請(qǐng)求的參數(shù),另外注意跳轉(zhuǎn)的uri的路徑,避免跨域問(wèn)題

四、簡(jiǎn)化模式

整體流程通授權(quán)碼模式

AuthorizationServerConfig.configure(ClientDetailsServiceConfigurer clients) 中代碼authorizedGrantTypes設(shè)置為 .authorizedGrantTypes("implicit")

測(cè)試
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:表示訪(fǎng)問(wèn)令牌,必選項(xiàng)。
token_type:表示令牌類(lèi)型,該值大小寫(xiě)不敏感,必選項(xiàng)。
expires_in:表示過(guò)期時(shí)間,單位為秒。如果省略該參數(shù),必須其他方式設(shè)置過(guò)期時(shí)間。
scope:表示權(quán)限范圍,如果與客戶(hù)端申請(qǐng)的范圍一致,此項(xiàng)可省略。
state:如果客戶(hù)端的請(qǐng)求中包含這個(gè)參數(shù),認(rèn)證服務(wù)器的回應(yīng)也必須一模一樣包含這個(gè)參數(shù)。
response_type:固定token

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容