SpringSecurity整合OAuth2

Lou.Chen
大约 4 分钟

Spring-Security整合OAuth2

基本配置

1、pom.xml

spring-security-oauth2

spring-boot-starter-security

spring-boot-starter-data-redis

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.2.5.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>org.lx</groupId>
	<artifactId>security-oauth</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>security-oauth</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<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.security.oauth</groupId>
			<artifactId>spring-security-oauth2</artifactId>
			<version>2.3.6.RELEASE</version>
		</dependency>

		<!--redis 存储token-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>

		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

2、yaml配置
spring:
#  redis配置
  redis:
    port: 6379
    password: xxxxxx
    host: 47.96.141.44
    database: 0 
3、controller
@RestController
public class controller {

    /**
     * 模拟admin用户
     * @return
     */
    @GetMapping("/admin/hello")
    public String admin() {
        return "hello admin";
    }

    /**
     * 模拟user用户
     * @return
     */
    @GetMapping("/user/hello")
    public String user() {
        return "user hello";
    }

    /**
     * 普通用户(登录即可访问)
     * @return
     */
    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }
}

4、核心配置
①授权服务器
/**
 * 授权服务器
 */
@Configuration
//开启授权服务器
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    /**
     * 注入认证管理器 来支持password的认证模式
     *
     * 在oauth2模式中,有四种不同的认证模式.
     * 第三方登录一般使用授权码模式
     * 前后端分离一般使用password模式
     */
    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Autowired
    private UserDetailsService userDetailsService;

    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 配置password模式
     * 配置用户
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//        配置在内存中
        clients.inMemory()
//                设置认证模式 设置客户端id
                .withClient("password")
//                配置授权模式 标准的oauth并不包含refresh_token。但是在springsecurity实现下refresh_token归为其中一种
//                获取token 和 刷新token的
                .authorizedGrantTypes("password", "refresh_token")
//                设置过期时间 1800s
                .accessTokenValiditySeconds(1800)
//                设置资源id
                .resourceIds("rid")
                .scopes("all")
//                需要的密码(加密之后)密码 123
                .secret("$2a$10$vMu/taZjUE/JPUUi8Aep/epAjQjT5XqJj1/fOKqH/n1k9x8DOnkW6");

    }

    /**
     * 令牌的存储
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(new RedisTokenStore(redisConnectionFactory))
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService);
    }


    /**
     *支持client id和 client secret 作登录认证
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.allowFormAuthenticationForClients();
    }
}

②资源服务器(提供资源,即访问的路径,引用的令牌)
/**
 * 资源服务器 提供资源
 */
@Configuration
//开启资源服务器
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter{

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
//        指定资源id
        resources.resourceId("rid")
//                这些资源基于令牌进行认证
                .stateless(true);
    }

    /**
     * 先去授权服务器获取token,再去访问资源
     * 在这里我们提供访问资源的路径
     * @param http
     * @throws Exception
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/admin/**").hasRole("admin")
                .antMatchers("/user/**").hasRole("user")
                .anyRequest().authenticated();
    }
}
③security配置
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    @Override
    protected UserDetailsService userDetailsService() {
        return super.userDetailsService();
    }

    /**
     * 配置模拟用户
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
			//密码123               .withUser("lc").password("$2a$10$vMu/taZjUE/JPUUi8Aep/epAjQjT5XqJj1/fOKqH/n1k9x8DOnkW6")
                .roles("admin")
                .and()
			//密码123                .withUser("zs").password("$2a$10$vMu/taZjUE/JPUUi8Aep/epAjQjT5XqJj1/fOKqH/n1k9x8DOnkW6")
                .roles("user");
    }

    /**
     * 拦截指定的请求
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.antMatcher("/oauth/**")
//                拦截像/oauth/**的请求
                .authorizeRequests()
//                符合/oauth/**
                .antMatchers("/oauth/**")
//                全部放行
                .permitAll()
                .and()
                .csrf().disable();
    }
}

5、请求过程详解
① 获取令牌

PUT: http://localhost:8080/oauth/token

content-type : x-www-form-urlencoded

请求参数==>

username 和 password 为登录的用户名和密码

  "key":"username","value":"lc"
  "key":"password","value":"123"
  "key":"grant_type","value":"password"
  "key":"client_id","value":"password"
  "key":"scope","value":"all"
  "key":"client_secret","value":"123"

结果: ==>

{
    "access_token": "d274cd88-1c84-4d6a-9da4-11ed734aed6f",
    "token_type": "bearer",
    //用户请求获得新的token的验证
    "refresh_token": "0a454a83-9bf5-4331-bf12-35d4ac519f30",
    //过期时间
    "expires_in": 1064,
    "scope": "all"
}
②请求资源接口

访问普通接口==>

GET: http://localhost:8080/hello?access_token=d274cd88-1c84-4d6a-9da4-11ed734aed6f

admin接口==>

GET: http://localhost:8080/admin/hello?access_token=d274cd88-1c84-4d6a-9da4-11ed734aed6f

user接口==>

GET: http://localhost:8080/user/hello?access_token=d274cd88-1c84-4d6a-9da4-11ed734aed6f

③获取新的token,旧token失效

POST: http://localhost:8080/oauth/token

请求参数==>

refresh_token 是之前用户登录 refresh_token的的值

  "key":"grant_type","value":"refresh_token"
  "key":"refresh_token","value":"0a454a83-9bf5-4331-bf12-35d4ac519f30"
  "key":"client_id","value":"password"
  "key":"client_secret","value":"123"

请求结果==>

{
    "access_token": "8b03dd6a-18c7-49c0-b2ab-e0962627608c",
    "token_type": "bearer",
    "refresh_token": "0a454a83-9bf5-4331-bf12-35d4ac519f30",
    "expires_in": 1799,
    "scope": "all"
}