项目1:SSM + Layui + Mysql8 公司测评系统
项目2:SpringBoot+Vue+ Mysql8 大学社团管理系统
项目3:搭建自用单点OOS服务
项目4:搭建一套自己的验证授权及资源访问服务
本文的记录了使用 Spring 提供的 Spring Security oAuth2 搭建一套验证授权及资源访问服务的详细步骤,实现企业微服务架构时能够有效的控制多个服务的统一登录、授权及资源保护工作。
Oauth2
oAuth 协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是 oAuth 的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此 oAuth 是安全的。
Spring Security
Spring Security 是一个安全框架,前身是 Acegi Security,能够为 Spring 企业应用系统提供声明式的安全访问控制。Spring Security 基于 Servlet 过滤器、IoC 和 AOP,为 Web 请求和方法调用提供身份确认和授权处理,避免了代码耦合,减少了大量重复代码工作。
【授权服务地址】/oauth/authorize?client_id=【客户端ID】&response_type=token
安全系数最高.授权码模式适用于有自己的服务器的应用,它是一个一次性的临时凭证,用来换取 access_token 和 refresh_token。
【授权服务器地址】/oauth/authorize?client_id=【客户端ID】&response_type=token
密码模式中,用户向客户端提供自己的用户名和密码。客户端使用这些信息,向 “服务商提供商” 索要授权。在这种模式中,用户必须把自己的密码给客户端,但是客户端不得储存密码。这通常用在用户对客户端高度信任的情况下,比如客户端是操作系统的一部分。
【授权服务器地址】/oauth/token?client_id=web_dev&client_secret=【客户端ID】&grant_type=password&username=【用户名】&password=【密码】
如果信任关系再进一步,或者调用者是一个后端的模块,没有用户界面的时候,可以使用客户端模式。鉴权服务器直接对客户端进行身份验证,验证通过后,返回 token。
【授权服务器地址】/oauth/token?client_id=【客户端Id】&client_secret=【客户端密码】&grant_type=client_credentials
新建一个
maven
工程,添加依赖如下。
xml
<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.0modelVersion>
<groupId>com.dingwengroupId>
<artifactId>dingwen-authartifactId>
<version>1.0-SNAPSHOTversion>
<modules>
<module>auth-authorization-servermodule>
<module>auth-resource-servermodule>
modules>
<packaging>pompackaging>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.1.8.RELEASEversion>
<relativePath/>
parent>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<java.version>1.8java.version>
<spring.cloud.version>Greenwich.SR1spring.cloud.version>
<spring.cloud.starter.oauth2.version>2.1.2.RELEASEspring.cloud.starter.oauth2.version>
<mybatis.plus.boot.starter.version>3.4.2mybatis.plus.boot.starter.version>
<mysql.connector.java.version>8.0.15mysql.connector.java.version>
<lombok.version>1.18.8lombok.version>
<spring.boot.starter.test>2.1.5.RELEASEspring.boot.starter.test>
<spring.boot.starter.web>2.1.5.RELEASEspring.boot.starter.web>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring.cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>${mybatis.plus.boot.starter.version}version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
<version>${lombok.version}version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>${mysql.connector.java.version}version>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-oauth2artifactId>
<version>${spring.cloud.starter.oauth2.version}version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<version>${spring.boot.starter.web}version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
<version>${spring.boot.starter.test}version>
dependency>
dependencies>
dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
xml
<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.0modelVersion>
<parent>
<groupId>com.dingwengroupId>
<artifactId>dingwen-authartifactId>
<version>1.0-SNAPSHOTversion>
parent>
<groupId>com.dingwen.auausegroupId>
<artifactId>auth-authorization-serverartifactId>
<version>1.0version>
<name>auth-authorization-servername>
<description>授权服务器description>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-oauth2artifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
<developers>
<developer>
<id>authid>
<name>dingwenname>
<email>[email protected]email>
developer>
developers>
project>
sql文件在工程的doc目录下,建表语句中有详细的每张表的字段含义。
package com.dingwen.auause.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.UserDetailsByNameServiceWrapper;
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.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
import javax.sql.DataSource;
/**
* 配置认证服务器
*
* @author dingwen
* 2021.05.12 14:37
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
private static final String CLIENT_ID = "web_dev";
private static final String SECRET = "dingwen_web";
private static final String AUTHORIZED_GRANT_TYPES = "authorization_code";
private static final String SCOPES = "write";
private static final String REDIRECT_URIS = "http:///www.baidu.com";
private final BCryptPasswordEncoder bCryptPasswordEncoder;
private final DataSource dataSource;
private final AuthenticationManager authenticationManager;
private final UserDetailsService userDetailsService;
@Autowired
AuthorizationServerConfiguration(BCryptPasswordEncoder bCryptPasswordEncoder,
DataSource dataSource,
AuthenticationManager authenticationManager,
UserDetailsService userDetailsService
) {
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
this.dataSource = dataSource;
this.authenticationManager = authenticationManager;
this.userDetailsService = userDetailsService;
}
/*
* 配置 TokenStore
* @return TokenStore
*/
@Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
/*
* jdbc 客户端服务
* @return ClientDetailsService
*/
@Bean
public ClientDetailsService jdbcClientDetails() {
// 必须是这个方法名称
return new JdbcClientDetailsService(dataSource);
}
/*
* 配置客户端
* @param clients
*
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 内存模式
/*clients //配置客户端
.inMemory() // 试用内存设置
.withClient(CLIENT_ID) //客户端标识
.secret(bCryptPasswordEncoder.encode(SECRET)) //客户端安全码
.authorizedGrantTypes(AUTHORIZED_GRANT_TYPES)//授权类型
.scopes(SCOPES)//授权范围
.redirectUris(REDIRECT_URIS);// 注册回调地址
*/
// 数据库模式
clients.withClientDetails(jdbcClientDetails());
}
// 设置令牌
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore())
.authenticationManager(authenticationManager)
.tokenServices(defaultTokenServices())
.userDetailsService(userDetailsService);
}
@Bean
public AuthorizationServerTokenServices defaultTokenServices() throws Exception {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setAuthenticationManager(this.authenticationManager);
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setClientDetailsService(jdbcClientDetails());
// access token有效期2个小时
defaultTokenServices.setAccessTokenValiditySeconds(60 * 60 * 2);
// refresh token有效期30天
defaultTokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 30);
// 支持使用refresh token刷新access token
defaultTokenServices.setSupportRefreshToken(true);
// 允许重复使用refresh token
defaultTokenServices.setReuseRefreshToken(true);
return defaultTokenServices;
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()");
security.checkTokenAccess("isAuthenticated()");
// 允许使用客户端id和客户端secret获取token
security.allowFormAuthenticationForClients();
}
}
Spring Security
配置package com.dingwen.auause.config;
import com.dingwen.auause.service.PermissionService;
import com.dingwen.auause.service.UserService;
import com.dingwen.auause.service.impl.UserDetailsServiceImpl;
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.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
import java.util.Arrays;
import java.util.Collections;
/**
* 服务器安全配置
*
* @author dingwen
* 2021.05.12 14:47
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true) //全局方法拦截
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
private final UserService userService;
private final PermissionService permissionService;
@Autowired
WebSecurityConfiguration(UserService userService, PermissionService permissionService) {
this.userService = userService;
this.permissionService = permissionService;
}
/*
* 加密方式
* @return BCryptPasswordEncoder
*/
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
ProviderManager authenticationManager;
authenticationManager = new ProviderManager(Collections.singletonList(authenticationProvider()));
return authenticationManager;
}
/*
* 自定义授权与认证
* @return UserDetailsService
*/
@Bean
public UserDetailsService userDetailsService() {
return new UserDetailsServiceImpl(userService, permissionService);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
/*auth.inMemoryAuthentication() //在内存中创建用户并进行加密
.withUser("dingwen")
.password(bCryptPasswordEncoder()
.encode("123456"))
.roles("USER")
.and()
.withUser("admin")
.password(bCryptPasswordEncoder()
.encode("123456"))
.roles("ADMIN");*/
// auth.authenticationProvider(authenticationProvider());
auth.userDetailsService(userDetailsService()); // 使用自定义用户授权
}
@Override
public void init(WebSecurity web) throws Exception {
super.init(web);
// 将 check_token 暴露出去,否则资源服务器访问时报 403 错误
web.ignoring().antMatchers("/oauth/check_token");
}
/**
* 认证
*/
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
//对默认的UserDetailsService进行覆盖
authenticationProvider.setUserDetailsService(userDetailsService());
return authenticationProvider;
}
}
xml
<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.0modelVersion>
<parent>
<groupId>com.dingwengroupId>
<artifactId>dingwen-authartifactId>
<version>1.0-SNAPSHOTversion>
parent>
<groupId>com.dingwen.auresegroupId>
<artifactId>auth-resource-serverartifactId>
<version>1.0version>
<name>auth-resource-servername>
<description>资源服务器description>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-oauth2artifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
<developers>
<developer>
<id>authid>
<name>dingwenname>
<email>[email protected]email>
developer>
developers>
project>
package com.dingwen.aurese.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
/**
* 资源服务器配置
*
* @author dingwen
* 2021.05.13 14:37
*/
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling() //异常处理器
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 禁用session
.and()
.authorizeRequests()
.antMatchers("/before").hasAnyAuthority("be"); // 用于测试访问/before资源的人需要有be权限
}
}
security:
oauth2:
client:
client-id: web-dev
client-secret: 123456
access-token-uri: http://localhost:9001/oauth/token # 请求令牌的地址
user-authorization-uri: http://localhost:9001/oauth/authorize # 授权地址
resource:
token-info-uri: http://localhost:9001/oauth/check_token # 检查 token 是否有效
【授权服务器地址】/oauth/authorize?client_id=web_dev&response_type=code
code
code
获取token
token
访问资源token
访问资源token
https://www.funtl.com/zh/spring-security-oauth2
https://gitee.com/dingwen-gitee/dingwen-auth