SpringBoot整合Spring Security和OAuth2
OAuth2介绍
OAuth协议为用户资源的授权提供了一个安全的、开放而又简易的标准。OAuth不需要用户提供用户名、密码等信息,就可以实现多个系统之间的资源共享。
OAuth2的四中授权模式
- 授权码模式
- 简易模式
- 密码模式
- 客户端模式
测试环境
- openjdk11
- springboot 2.3.1
- spring security
- spring cloud oauth2
项目地址
https://gitee.com/randomObject/springboot_security_oauth
测试项目结构
因为spring OAuth2封装好了常用的认证授权操作,所有需要使用OAuth2提供的表结构。项目中有提供。
父工程pom文件
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.gitee.randomobject</groupId>
<artifactId>springboot_security_oauth</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>oauth_resources</module>
<module>oauth_server</module>
</modules>
<!--spring boot项目-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<!--依赖版本管理-->
<properties>
<java.version>11</java.version>
<spring-cloud.version>Greenwich.RELEASE</spring-cloud.version>
</properties>
<!--依赖管理-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!--lombok插件-->
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
</dependencies>
<!--spring官方仓库,速度更好-->
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
父工程充当springcloud版本控制,和repository仓库管理,以及其他一些公用的配置和依赖。
-
oauth_server项目介绍
该项目作为服务认证和授权端。该端充当用户的认证及授权和资源注册管理角色。
oauth_server项目的pom.xml内容
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springboot_security_oauth</artifactId>
<groupId>com.gitee.randomobject</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>oauth_server</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
</dependencies>
</project>
oauth_server的关键配置
oauth_server的核心配置在项目的config包下
-
WebSecutityConfig类
package com.gitee.randomobject.config;
import com.gitee.randomobject.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
@EnableWebSecurity
public class WebSecutityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserService userService;//认证业务对象,当前待验证的用户是否有访问该资源的权限
@Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginProcessingUrl("/login")//跳转页面授权
.permitAll()
.and()
.csrf()
.disable();
}
/** AuthenticationManager对象在Oauth2{@link OauthServerConfig}
* 认证服务中要使用,提前放入IOC容器中以供使用
*/
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManager();
}
}
-
OauthServerConfig类
package com.gitee.randomobject.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
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.approval.ApprovalStore;
import org.springframework.security.oauth2.provider.approval.JdbcApprovalStore;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import javax.sql.DataSource;
import javax.swing.*;
import javax.xml.crypto.Data;
@Configuration
@EnableAuthorizationServer
public class OauthServerConfig extends AuthorizationServerConfigurerAdapter {
//数据库连接池对象
@Autowired
private DataSource dataSource;
//认证业务对象
@Autowired
private UserDetailsService userDetailsService;
//授权模式专用对象
@Autowired
private AuthenticationManager authenticationManager;
//客户端信息来源
@Bean
public JdbcClientDetailsService clientDetailsService() {
return new JdbcClientDetailsService(dataSource);
}
//token保存策略
@Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
//授权信息保存策略
@Bean
public ApprovalStore approvalStore() {
return new JdbcApprovalStore(dataSource);
}
//授权码模式数据来源
@Bean
public AuthorizationCodeServices authorizationCodeServices() {
return new JdbcAuthorizationCodeServices(dataSource);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.allowFormAuthenticationForClients();
security.checkTokenAccess("isAuthenticated()");
}
//客户端信息的数据库来源
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetailsService());
}
//Oauth的主配置信息--整合以上部分配置
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.userDetailsService(userDetailsService)//添加刷新token,refresh_token功能
.approvalStore(approvalStore())
.authenticationManager(authenticationManager)
.authorizationCodeServices(authorizationCodeServices())
.tokenStore(tokenStore());
}
}
oauth_resources项目结构
oauth_resources作为资源端提供资源服务,该项目pom.xml内容如下
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springboot_security_oauth</artifactId>
<groupId>com.gitee.randomobject</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>oauth_resources</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
</dependencies>
</project>
-
OauthResourcesConfig配置类
package com.gitee.randomobject.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import javax.sql.DataSource;
@Configuration
@EnableResourceServer
public class OauthResourcesConfig extends ResourceServerConfigurerAdapter {
@Autowired
private DataSource dataSource;
/**
* 指定token的持久化策略
* @return JdbcTokenStore
*/
@Bean
public TokenStore jdbcTokenStore() {
return new JdbcTokenStore(dataSource);
}
/**
* 指定当前资源的ID和存储方案
* @param resources
* @throws Exception
*/
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
//可以通过配置文件读取的方式加载resourceId,此处为方便,直接写死了
resources.resourceId("product_api").tokenStore(jdbcTokenStore());
}
@Override
public void configure(HttpSecurity http) throws Exception {
/**
* 固定配置,无需记住,了解即可。
*/
http.authorizeRequests()
//配置不同访问请求需要的权限
.antMatchers(HttpMethod.GET, "/**").access("#oauth2.hasAnyScope("read")")
.antMatchers(HttpMethod.POST, "/**").access("#oauth2.hasAnyScope("write")")
.antMatchers(HttpMethod.PATCH, "/**").access("#oauth2.hasAnyScope("write")")
.antMatchers(HttpMethod.PUT, "/**").access("#oauth2.hasAnyScope("write")")
.antMatchers(HttpMethod.DELETE, "/**").access("#oauth2.hasAnyScope("write")")
.and()
.headers().addHeaderWriter((request, response) -> {//处理跨域请求
response.addHeader("Access-Control-Allow-Origin", "*");//允许跨域
if (request.getMethod().equals("OPTIONS")){//如果是跨域的预检请求,则原封不动向下传递请求投信息
response.setHeader("Access-Control-Allow-Methods",request.getHeader("Access-Control-Allow-Method"));
response.setHeader("Access-Control-Allow-Headers",request.getHeader("Access-Control-Allow-Headers"));
}
});
}
}
测试流程
- 数据库表的数据准备
- 登录页面获取授权码
http://localhost:8080/oauth/authorize?response_type=code&client_id=test_one
调用回调地址,返回授权码
根据授权码获取token
根据获得的token获取资源
刷新token