oauth2.0系统学习3-简单搭建spring sercurity+oauth2.0的框架
一、简单搭建spring sercurity+oauth2.0 的框架
- oauth_parent:父工程
- oauth-authorizationServer:认证服务器
- oauth-ResourceServer:资源服务器
oauth_parent:父工程代码(就是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 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.1.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>oauth_parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>oauth_parent</name>
<packaging>pom</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.interceptor</groupId>
<artifactId>javax.interceptor-api</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.0</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.0.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.1.3.RELEASE</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
二、搭建一个认证服务器 oauth-authorizationServer
2.1、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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>oauth_parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>oauth-authorizationserver</artifactId>
<name>oauth-authorizationserver</name>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.2、application.properties
spring.application.name=oauth-authorizationServer
server.port=8081
spring.datasource.url = jdbc:mysql://localhost:3306/testoauth?useUnicode=true
spring.datasource.username=root
spring.datasource.password=ylx123
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
2.3、UserAuthorizeBean
package com.example.oauthauthorizationserver.business.bean;
import lombok.Data;
/**
* @Description 请描述下该类是做什么的
* @Author <a href="mailto:yuanlx@smartdot.com.cn">袁凌霄</a>
* @Date 2021/9/13 22:23
* @Verson 1.0
**/
@Data
public class UserBean {
private Long id;
private String userName;
private String password;
}
2.4、UserAuthorizeBean
package com.example.oauthauthorizationserver.business.bean;
import lombok.Data;
/**
* @Description 请描述下该类是做什么的
* @Author <a href="mailto:yuanlx@smartdot.com.cn">袁凌霄</a>
* @Date 2021/9/13 22:18
* @Verson 1.0
**/
@Data
public class UserAuthorizeBean {
private Long id;
private Long userId;
private String authorizeCode;
}
2.5、AuthorizationServer
package com.example.oauthauthorizationserver.business.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.authentication.AuthenticationManager;
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.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices;
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.InMemoryTokenStore;
/**
* @Description 请描述下该类是做什么的
* @Author <a href="mailto:yuanlx@smartdot.com.cn">袁凌霄</a>
* @Date 2021/9/13 22:23
* @Verson 1.0
**/
@Configuration
@EnableAuthorizationServer // 配置授权服务。
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
// 用来配置客户端详情服务
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 这里是第三方合作用户的客户id,秘钥的配置
// 使用in-memory存储
clients.inMemory()
// client_id,用户账号
.withClient("c1")
// 客户端密钥
.secret(new BCryptPasswordEncoder().encode("secret"))
// 资源列表,资源标识
.resourceIds("res1")
// 授权类型(4种)
.authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit",
"refresh_token")
// 客戶端允许的授权范围
.scopes("all")
// false跳转到授权页面,让用户点击授权,如果是true,相当于自动点击授权,就不跳转授权页面
.autoApprove(false)//
// 加上验证回调地址,返回授权码信息
.redirectUris("http://www.baidu.com");
// 如果有多个用户,配置多个客户详情
// .and().withClient()
}
@Autowired
// 令牌存储策略
private TokenStore tokenStore;
@Autowired
// 客户端详情服务,也就是configure(ClientDetailsServiceConfigurer clients)方法
private ClientDetailsService clientDetailsService;
// 令牌管理服务
@Bean
public AuthorizationServerTokenServices tokenService() {
DefaultTokenServices service = new DefaultTokenServices();
service.setClientDetailsService(clientDetailsService);
service.setSupportRefreshToken(true);// 支持刷新
service.setTokenStore(tokenStore);// 令牌存储
service.setAccessTokenValiditySeconds(7200); // 令牌默认有效期2小时
service.setRefreshTokenValiditySeconds(259200); // 刷新令牌默认有效期3天
return service;
}
@Autowired
// 授权码服务
private AuthorizationCodeServices authorizationCodeServices;
@Autowired
// 认证管理
private AuthenticationManager authenticationManager;
@Autowired
// 令牌管理服务
private AuthorizationServerTokenServices authorizationServerTokenServices;
@Override
// 用来配置令牌(token)的访问端点
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
// 密码模式需要
.authenticationManager(authenticationManager)
// 授权码模式需要
.authorizationCodeServices(authorizationCodeServices)
// 令牌管理服务
.tokenServices(authorizationServerTokenServices).allowedTokenEndpointRequestMethods(HttpMethod.POST);// 允许post提交
}
@Bean
// 授权码服务器
public AuthorizationCodeServices authorizationCodeServices() {
// 授权码模式的授权码采用内存方式存储
return new InMemoryAuthorizationCodeServices();
}
@Override
// 用来配置令牌端点的安全约束,拦截规则
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
// 提供公有密匙的端点,如果你使用JWT令牌的话, 允许
.tokenKeyAccess("permitAll()")
// oauth/check_token:用于资源服务访问的令牌解析端点,允许
.checkTokenAccess("permitAll()")
// 表单认证,申请令牌
.allowFormAuthenticationForClients();
}
@Bean
// 令牌存储策略
public TokenStore tokenStore() {
// 内存存储,普通令牌
return new InMemoryTokenStore();
}
}
2.6、WebSecurityConfig
package com.example.oauthauthorizationserver.business.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.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @Description 请描述下该类是做什么的
* @Author <a href="mailto:yuanlx@smartdot.com.cn">袁凌霄</a>
* @Date 2021/9/13 22:24
* @Verson 1.0
**/
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
// 认证管理器
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
// 密码编码器
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// 安全拦截机制
@Override
protected void configure(HttpSecurity http) throws Exception {
http.
csrf().disable().
authorizeRequests().
antMatchers("/admin/p1").
hasAnyAuthority("p1").
antMatchers("/user/p2").
hasAnyAuthority("p2").
antMatchers("/login*")
.permitAll().
anyRequest().
authenticated().
and().
formLogin();
}
}
2.7、UserDao
package com.example.oauthauthorizationserver.business.dao;
import com.example.oauthauthorizationserver.business.bean.UserAuthorizeBean;
import com.example.oauthauthorizationserver.business.bean.UserBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import java.util.ArrayList;
import java.util.List;
/**
* @Description 请描述下该类是做什么的
* @Author <a href="mailto:yuanlx@smartdot.com.cn">袁凌霄</a>
* @Date 2021/9/13 22:26
* @Verson 1.0
**/
@Repository
public class UserDao {
@Autowired
JdbcTemplate jdbcTemplate;
// 根据账号查询用户信息
public UserBean getUserByUsername(String username) {
String sql = "select * from user where user_name = ?";
// 连接数据库查询用户
List<UserBean> list = jdbcTemplate.query(sql, new Object[] { username },
new BeanPropertyRowMapper<>(UserBean.class));
if (list != null && list.size() == 1) {
return list.get(0);
}
return null;
}
// 根据用户id查询用户权限
public List<String> getAuthorize(Long userId) {
String sql = "SELECT * FROM user_authorize WHERE user_id =?";
List<UserAuthorizeBean> list = jdbcTemplate.query(sql, new Object[] { userId },
new BeanPropertyRowMapper<>(UserAuthorizeBean.class));
List<String> authorizes = new ArrayList<>();
list.forEach(c -> authorizes.add(c.getAuthorizeCode()));
return authorizes;
}
}
2.8、SpringDataUserDetailsService
package com.example.oauthauthorizationserver.business.service;
import com.example.oauthauthorizationserver.business.bean.UserBean;
import com.example.oauthauthorizationserver.business.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @Description 请描述下该类是做什么的
* @Author <a href="mailto:yuanlx@smartdot.com.cn">袁凌霄</a>
* @Date 2021/9/13 22:39
* @Verson 1.0
**/
@Service
public class SpringDataUserDetailsService implements UserDetailsService {
@Autowired
UserDao userDao;
// 根据账号查询用户信息,
// 通过@Service将SpringDataUserDetailsService注入容器,通过UserDetailsService接口表明该类的类型是UserDetailsService
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 将来连接数据库根据账号查询用户信息
UserBean bean = userDao.getUserByUsername(username);
if (bean == null) {
// 如果用户查不到,返回null,由provider来抛出异常
return null;
}
// 查询当前数据库的用户资源权限
List<String> authorize = userDao.getAuthorize(bean.getId());
String[] authorizeArr = new String[authorize.size()];
authorize.toArray(authorizeArr);
// 添加权限
UserDetails userDetails = User.withUsername(bean.getUserName()).password(bean.getPassword())
.authorities(authorizeArr).build();
return userDetails;
}
}
2.9、mysql数据库中创建两张表
user:用户账号密码表
user_authorize:用户资源授权表
2.10、理论,解读代码
L1. OAuth2.0授权服务器的开启
-
@EnableAuthorizationServer注解
-
继承AuthorizationServerConfigurerAdapter
L2. AuthorizationServerConfigurerAdapter:
- 要配置以下几个类,通过重写以下方法来实现
L3. ClientDetailsServiceConfigurer
- 用来配置客户端详情服务(ClientDetailsService )
- ClientDetailsService负责查找ClientDetails
- 可通过内存或者数据库来配置
- 客户端详情服务
- 第三方服务器来授权服务器申请访问资源。通过授权后,颁发clientId,secret给第三方服务器,表示第三方服务器是信任的服务器。
ClientDetails重要属性列表:
-
clientId:标识客户的Id
-
secret:客户端秘钥
-
scope:限制客户端的访问范围
- 默认空:拥有全部的访问范围。
-
authorizedGrantTypes:此客户端可以使用的授权类型
- 默认为空
-
authorities:客户端可以使用的权限
- 基于SpringSecurityauthorities
ClientDetails介绍
- 能够在应用程序运行的时候进行更新
- 可以通过访问底层的存储服务
- 客户端详情存储在一个关系数据库的表中
- 实现方式
- 使用JdbcClientDetailsService
- 自己实现ClientRegistrationService接口
- 自己实现ClientDetailsService接口
具体代码
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 这里是第三方合作用户的客户id,秘钥的配置
// 使用in-memory存储
clients.inMemory()
// client_id,用户账号
.withClient("c1")
// 客户端密钥
.secret(new BCryptPasswordEncoder().encode("secret"))
// 资源列表,资源标识
.resourceIds("res1")
// 授权类型(4种)
.authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit",
"refresh_token")
// 客戶端允许的授权范围
.scopes("all")
// false跳转到授权页面,让用户点击授权,如果是true,相当于自动点击授权,就不跳转授权页面
.autoApprove(false)//
// 加上验证回调地址,返回授权码信息
.redirectUris("http://www.baidu.com");
// 如果有多个用户,配置多个客户详情
// .and().withClient()
}
L4. AuthorizationServerTokenServices
- 令牌管理服务
- 定义管理令牌的操作
- 令牌
- 用来加载身份信息
- 包含了这个令牌的相关权限。
- 这个接口的实现,则需要继承DefaultTokenServices
DefaultTokenServices
- 可以修改令牌的格式
- 可以修改令牌的存储。
- 默认的,当它尝试创建一个令牌使用随机值来进行填充的,除了持久化令牌是委托一个TokenStore接口来实现以外,这个类几乎帮你做了所有的事情。
TokenStore
-
默认实现:InMemoryTokenStore
- 所有的令牌是被保存在了内存中
- 它可以完美的工作在单服务器上(即访问并发量压力不大的情况下,并且它在失败的时候不会进行备份)
-
JdbcTokenStore
- 基于JDBC的实现版本,令牌会被保存进关系型数据库
- 注意点:需要把"spring-jdbc"这个依赖加入到你的classpath当中。
-
JwtTokenStore
- 它可以把令牌相关的数据进行编码(因此对于后端服务来说,它不需要进行存储,这将是一个重大优势)
- 缺点:
- 撤销一个已经授权令牌非常困难,所以它通常用来处理一个生命周期较短的令牌以及撤销刷新令牌(refresh_token)
- 这个令牌占用的空间会比较大,如果你加入了比较多用户凭证信息。JwtTokenStore不会保存任何数据,但是它在转换令牌值以及授权信息方面与DefaultTokenServices所扮演的角色是一样的。
L5. AuthorizationServerEndpointsConfigurer
- 可以完成令牌服务
- 可以完成令牌endpoint配置
配置授权类型(GrantTypes)
- authenticationManager:认证管理器
- 选择密码(password)授权类型的时候,请设置这个属性注入一个AuthenticationManager对象。
- userDetailsService:
- 设置UserDetailsService接口的实现类(用户登录验证)
- 或者你可以把这个东西设置到全局域上面去(例如GlobalAuthenticationManagerConfigurer这个配置对象),当你设置了这个之后,那么"refresh_token"即刷新令牌授权类型模式的流程中就会包含一个检查,用来确保这个账号是否仍然有效。
- authorizationCodeServices:设置授权码服务
- 用于"authorization_code"授权码类型模式
- implicitGrantService:设置隐式授权模式,用来管理隐式授权模式的状态。
- tokenGranter:
- 设置TokenGranter接口实现,授权将会交由你来完全掌控,并且会忽略掉上面的这几个属性
- 这个属性一般是用作拓展用途的,即标准的四种授权模式已经满足不了你的需求的时候,才会考虑使用这个。
具体代码
@Override
// 用来配置令牌(token)的访问端点
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
// 密码模式需要
.authenticationManager(authenticationManager)
// 授权码模式需要
.authorizationCodeServices(authorizationCodeServices)
// 令牌管理服务
.tokenServices(authorizationServerTokenServices).allowedTokenEndpointRequestMethods(HttpMethod.POST);// 允许post提交
}
配置授权端点的URL(EndpointURLs)
- AuthorizationServerEndpointsConfigurer有一个pathMapping()的方法用来配置端点URL链接
- 它有两个参数
- 第一个参数:String类型的,这个端点URL的默认链接。
- 第二个参数:String类型的,你要进行替代的URL链接。
- 以上的参数都将以"/"字符为开始的字符串,
- 框架的默认URL链接如下列表,可以作为这个pathMapping()方法的第一个参数
- /oauth/authorize:授权端点。
- /oauth/token:令牌端点。
- /oauth/confirm_access:用户确认授权提交端点。
- /oauth/error:授权服务错误信息端点。
- /oauth/check_token:用于资源服务访问的令牌解析端点。
- /oauth/token_key:提供公有密匙的端点,如果你使用JWT令牌的话。需要注意的是授权端点这个URL应该被SpringSecurity保护起来只供授权用户访问。
L7. authorizationCodeServices
- 授权码服务的存储方式
具体代码
@Bean
// 授权码服务器
public AuthorizationCodeServices authorizationCodeServices() {
// 授权码模式的授权码采用内存方式存储
return new InMemoryAuthorizationCodeServices();
}
L8. AuthorizationServerSecurityConfigurer
-
令牌端点的安全约束
-
tokenkey:/oauth/token_key端点完全公开。
-
checkToken:/oauth/check_token端点完全公开。
-
allowFormAuthenticationForClients:允许表单认证
具体代码
@Override
// 用来配置令牌端点的安全约束,拦截规则
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
// 提供公有密匙的端点,如果你使用JWT令牌的话, 允许
.tokenKeyAccess("permitAll()")
// oauth/check_token:用于资源服务访问的令牌解析端点,允许
.checkTokenAccess("permitAll()")
// 表单认证,申请令牌
.allowFormAuthenticationForClients();
}
2.11、验证认证服务器
001)、授权码模式
http://localhost:8081/oauth/authorize?client_id=c1&response_type=code&scope=all&redirect_uri=http://www.baidu.com
需要先登录(Security的原因)
用户授权
返回授权码
请求token
http://localhost:8081/oauth/token?client_id=c1&client_secret=secret&grant_type=authorization_code&code=Nsitob&redirect_uri=http://www.baidu.com
002)、简化模式
http://localhost:8081/oauth/authorize?client_id=c1&response_type=token&scope=all&redirect_uri=http://www.baidu.com
003) 、密码模式
http://localhost:8081/oauth/token?client_id=c1&client_secret=secret&grant_type=password&username=admin&password=admin
004) 、客户端模式
http://localhost:8081/oauth/token?client_id=c1&client_secret=secret&grant_type=client_credentials
三、搭建一个资源服务器 oauth-ResourceServer
3.1、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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>oauth_parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>oauth-resourceserver</artifactId>
<name>oauth-resourceserver</name>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3.2、application.properties
spring.application.name=oauth-ResourceServer
server.port=8082
3.3、ResouceServerConfig
package com.example.oauthresourceserver.business.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
/**
* @Description 请描述下该类是做什么的
* @Author <a href="mailto:yuanlx@smartdot.com.cn">袁凌霄</a>
* @Date 2021/9/13 23:08
* @Verson 1.0
**/
@Configuration
@EnableResourceServer
public class ResouceServerConfig extends ResourceServerConfigurerAdapter {
// 授权服务的资源列表
public static final String RESOURCE_ID = "res1";
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources
// 资源 id
.resourceId(RESOURCE_ID)
// 令牌服务
.tokenServices(tokenService())
.stateless(true);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().antMatchers("/**")
// 所有的访问,授权访问都要是all,和认证服务器的授权范围一一对应
.access("#oauth2.hasScope('all')")
//去掉防跨域攻击
.and().csrf().disable()
//session管理
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
@Bean
// 资源服务令牌解析服务
public ResourceServerTokenServices tokenService() {
// 使用远程服务请求授权服务器校验token,必须指定校验token 的url、client_id,client_secret
RemoteTokenServices service = new RemoteTokenServices();
service.setCheckTokenEndpointUrl("http://localhost:8081/oauth/check_token");
service.setClientId("c1");
service.setClientSecret("secret");
return service;
}
}
3.4、WebSecurityConfig
package com.example.oauthresourceserver.business.config;
import org.springframework.context.annotation.Bean;
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.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @Description 请描述下该类是做什么的
* @Author <a href="mailto:yuanlx@smartdot.com.cn">袁凌霄</a>
* @Date 2021/9/13 23:09
* @Verson 1.0
**/
//由于Spring boot starter自动装配机制,这里无需使用@EnableWebSecurity
//@EnableWebSecurity
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true) // 方法授权
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
// 密码编码器,不加密
@Bean
public PasswordEncoder passwordEncoder() {
// return NoOpPasswordEncoder.getInstance(); // 不加密
return new BCryptPasswordEncoder();// BCryptPasswordEncoder加密
}
// web url 拦截规则
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()// 所有其他请求必须认证通过
.and().formLogin().loginPage("/login").successForwardUrl("/login-success")// 自定义登录成功的页面地址
.permitAll().and().logout().permitAll().and()// 会话管理
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);// 会话管理
}
}
3.5、LoginController
package com.example.oauthresourceserver.business.controller;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Description 请描述下该类是做什么的
* @Author <a href="mailto:yuanlx@smartdot.com.cn">袁凌霄</a>
* @Date 2021/9/13 23:10
* @Verson 1.0
**/
@RestController
public class LoginController {
@RequestMapping(value = "/login-success")
public String loginSuccess() {
return getUsername() + " login-success 登录成功";
}
/**
* 测试资源1
*
* @return
*/
@GetMapping(value = "/admin/p1")
@PreAuthorize("hasAuthority('p1')")//拥有p1权限才可以访问
public String r1() {
return " /admin/p1 " + getUsername() + "访问资源1";
}
/**
* 测试资源2
*
* @return
*/
@GetMapping(value = "/user/p2")
@PreAuthorize("hasAuthority('p2')")//拥有p2权限才可以访问
public String r2() {
return "/user/p2 " + getUsername() + "访问资源2";
}
// 获取当前用户信息
private String getUsername() {
String username = null;
// 当前认证通过的用户身份
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// 用户身份
Object principal = authentication.getPrincipal();
if (principal == null) {
username = "匿名";
}
if (principal instanceof UserDetails) {
UserDetails userDetails = (UserDetails) principal;
username = userDetails.getUsername();
} else {
username = principal.toString();
}
return username;
}
}
002)、代码解读和理论
L1. OAuth2.0资源服务器的开启
- @EnableResourceServer注解
- 会自动增加了一个类型为OAuth2AuthenticationProcessingFilter的过滤器链
- 继承ResourceServerConfigurerAdapter
L2. ResourceServerSecurityConfigurer
-
tokenServices:
- ResourceServerTokenServices类的实例
- 用来实现令牌服务
-
tokenStore:
- TokenStore类的实例,指定令牌如何访问,与tokenServices配置可选
-
resourceId
- 这个资源服务的ID,这个属性是可选的,推荐设置并在授权服务中进行验证
-
tokenExtractor
- 令牌提取器用来提取请求中的令牌
具体代码:
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources
// 资源 id
.resourceId(RESOURCE_ID)
// 令牌服务
.tokenServices(tokenService())
.stateless(true);
}
L3. HttpSecurity配置这个与Spring Security类似
具体代码:
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().antMatchers("/**")
// 所有的访问,授权访问都要是all,和认证服务器的授权范围一一对应
.access("#oauth2.hasScope('all')")
//去掉防跨域攻击
.and().csrf().disable()
//session管理
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
L4. ResourceServerTokenServices
-
DefaultTokenServices
- 授权服务和资源服务在同一个应用程序上可以使用
- 在资源服务器本地配置令牌存储、解码、解析方式使用
-
RemoteTokenServices
- 资源服务器通过HTTP请求来解码令牌
- 请求端点:/oauth/check_token
具体代码:
@Bean
// 资源服务令牌解析服务
public ResourceServerTokenServices tokenService() {
// 使用远程服务请求授权服务器校验token,必须指定校验token 的url、client_id,client_secret
RemoteTokenServices service = new RemoteTokenServices();
service.setCheckTokenEndpointUrl("http://localhost:8081/oauth/check_token");
service.setClientId("c1");
service.setClientSecret("secret");
return service;
}
003)、测试
获取admin账号token
http://localhost:8081/oauth/token?client_id=c1&client_secret=secret&grant_type=password&username=admin&password=admin
获取user账号token
http://localhost:8081/oauth/token?client_id=c1&client_secret=secret&grant_type=password&username=user&password=user
检查token的信息:token参数为第一步获取时候的值
admin:
http://localhost:8081/oauth/check_token?token=474beb14-45b2-46f6-a4c7-361611730f76
user:
http://localhost:8081/oauth/check_token?token=10d95d5d-1e45-4c6c-a1aa-7031970b7dcc
admin请求可以访问的资源
头部信息中添加 Authorization,值为:“Bearer ” + token
admin请求不可以访问的资源
头部信息中添加 Authorization,值为:“Bearer ” + token
user请求可以访问的资源
头部信息中添加 Authorization,值为:“Bearer ” + token
user请求不可以访问的资源
头部信息中添加 Authorization,值为:“Bearer ” + token