Spring Security + OAuth2.0
1.OAuth2.0解释
OAuth是 Open Authorization的简写。
OAuth协议为用户资源的授权提供了一个安全的、开放而又简易的标准(开放授权标准)。
它允许用户授权第三方应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方应用或分享他们数据的所有内容。
2. OAuth2.0协议包含的角色
- 资源所有者(Resource Owner):
能够授予对受保护资源的访问权限的实体。通常是用户(user),也可以是应用程序,即该资源的拥有者。
- 认证服务器(Authorization server):
又称为授权服务器,认证成功后会给客户端发放令牌(),作为客户端访问资源服务器的凭据。
服务器认证成功后向客户端颁发访问令牌(access_token),作为客户端访问资源服务器的凭据,并验证资源所有者并获得授权。
简单点说就是登录功能,用于服务提供者对资源拥有的身份进行认证,对访问资源进行授权。
- 资源服务器(Resource server)
托管受保护资源的服务器,能够接受并使用访问令牌响应受保护的资源请求。
- 第三方应用程序(Third-party application):
- 示例中的浏览器、微信客户端
又称为客户端(client),本身不存储资源,需要通过资源拥有者的授权去请求资源服务器的资源。
3. OAuth2.0的四种授权模式
-
授权码模式(authorization code):
功能最完整、流程最严密的授权模式。特点是通过第三方应用的后台服务器,与服务提供平台的认证服务器进行互动获取资源。
-
简化模式(implicit):
不通过第三方应用服务器,直接在浏览器中向认证服务器申请token令牌,跳过了授权码这个步骤。所有步骤在浏览器中完成,token对用户可见,且第三方应用不需要认证。---不推荐使用
-
密码模式(resource owner password credentials):
用户向第三方应用提供自己的用户名和密码。第三方应用使用这些信息,向服务提供平台索要授权。在这种模式中,用户必须把自己的密码给第三方应用,但是第三方应用不得储存密码。这通常用在用户对第三方应用高度信任的情况下,比如第三方应用是操作系统的一部分,或者由一个著名公司出品。而认证服务器只有在其他授权模式无法执行的情况下,才能考虑使用这种模式。
-
客户端模式(client credentials):
指第三方应用以自己的名义,而不是以用户的名义,向服务提供平台进行认证。严格地说,客户端模式并不属于OAuth框架所要解决的问题。在这种模式中,用户直接向第三方应用注册,第三方应用以自己的名义要求服务提供平台提供服务,其实不存在授权问题。
4.认证服务器的实现
依赖包之间的关系:
spring-security-oauth2 -> 被废弃,建议不使用,否则后期无法维护
spring-security-oauth2-autoconfigure -> 自动配置,没有用处
spring-boot-starter-oauth2-client -> 最新
spring-boot-starter-oauth2-resource-server -> 最新
spring-cloud-starter-oauth2 -> 引用 spring-security-oauth2,但尚未标注被废弃
4.1 认证服务器对外暴露的接口
/oauth/authorize(授权端,授权码模式使用)
/oauth/token(令牌端,获取 token)
/oauth/check_token(资源服务器用来校验token)
/oauth/confirm_access(用户发送确认授权)
/oauth/error(认证失败)
/oauth/token_key(如果使用JWT,可以获的公钥用于 token 的验签)
4.2 认证服务器的搭建
认证服务器中Spring Security OAuth2 Token存储方式有三种方式:
- InMemory适用场景:单机;无大的访问量;可以承受重启丢失问题;
- JDBC适用场景:分布式;无超大访问量;永久存储;
- Redis适用场景:分布式;大访问或小访问都适合;缓存机器不崩溃token一般不丢失;
4.3 基于内存的的认证服务器
pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
</parent>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<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.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
1.UserDetailsService的实现类
@Slf4j
public class MyUserDetailsService implements UserDetailsService {
Map<String, String> user = new HashMap();
Map<String, List<String>> userRole = new HashMap<>();
Map<String,List<String>> roleResources = new HashMap<>();
{
// 用户映射
user.put("admin", "111111");
user.put("user", "111111");
user.put("noaccess", "111111");
//hasRole 的处理逻辑和 hasAuthority 似乎是一样的,只是hasRole 这
// 里会自动给传入的字符串前缀(默认是ROLE_ ),
// 使用 hasAuthority 更具有一致性,不用考虑要不要加 ROLE_ 前缀,
// 在UserDetailsService类的loadUserByUsername中查询权限,也不需要手动增加。
// 在SecurityExpressionRoot 类中hasAuthority 和 hasRole
// 最终都是调用了 hasAnyAuthorityName 方法。
// 用户角色
userRole.put("admin", Arrays.asList("ROLE_admin"));
userRole.put("user", Arrays.asList("query"));
//角色和资源
roleResources.put("/hello",Arrays.asList("admin","query"));
roleResources.put("/helloadmin",Arrays.asList("admin"));
roleResources.put("/hellouser",Arrays.asList("admin","query"));
}
/**
* 通过资源获取资源需要的角色
* @param resources
* @return
*/
public List<String> getRoleByResources(String resources) {
return roleResources.get(resources);
}
@Autowired
PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("MyUserDetailsService.loadUserByUsername 开始");
if (!user.containsKey(username)) {
throw new UsernameNotFoundException("username is not exists");
}
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
if (userRole.containsKey(username)) {
authorities = userRole.get(username).stream().map(row -> new SimpleGrantedAuthority(row))
.collect(Collectors.toList());
}
return new User(username, passwordEncoder.encode(user.get(username)), authorities);
}
}
2. WebSecurityConfigurerAdapter的配置
@Configuration
@EnableWebSecurity
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
/**
* @return 注:自定义密码编码器,适用于springsecurity和oauth2
*/
@Bean
public BCryptPasswordEncoder getPasswordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* @return
* @throws Exception 注 : 配置认证管理器,springsecurity通过认证管理器来实现认证
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManager();
}
@Bean
MyUserDetailsService myUserDetailsService() {
return new MyUserDetailsService();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//todo 表单登录
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginProcessingUrl("/login")
.permitAll()
.and()
.csrf()
.disable();
http.userDetailsService(myUserDetailsService());
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**");
web.ignoring().antMatchers("/js/**");
web.ignoring().antMatchers("/fonts/**");
}
}
3.AuthorizationServerConfigurerAdapter的配置
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter {
/**
* 用户认证 Manager
*/
@Autowired
private AuthenticationManager authenticationManager;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//TODO 可以内存模式也可以存储在数据库中,更加方便
clients.inMemory()
//客户端id
.withClient("mytest")
//客户端秘钥
.secret(new BCryptPasswordEncoder().encode("123456"))
//资源id,唯一,比如订单服务作为一个资源,可以设置多个
.resourceIds("res1")
//授权模式,总共四种,1. authorization_code(授权码模式)、password(密码模式)、client_credentials(客户端模式)、implicit(简化模式)
//refresh_token并不是授权模式,唯一的作用是启用刷新token
.authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")
//允许的授权范围,客户端的权限,这里的all只是一种标识,可以自定义,为了后续的资源服务进行权限控制
.scopes("all")
//false 则跳转到授权页面
.autoApprove(false)
//授权码模式的回调地址
.redirectUris("https://www.baidu.com");
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer
//表示支持 client_id 和 client_secret 做登录认证
.allowFormAuthenticationForClients()
//开启/oauth/token_key验证端口权限访问
.tokenKeyAccess("permitAll()")
//开启/oauth/check_token验证端口认证权限访问
.checkTokenAccess("permitAll()");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// 自定义 加载用户信息的接口
endpoints
//允许POST和Get提交访问令牌
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
//授权码模式所需要的authorizationCodeServices
.authorizationCodeServices(authorizationCodeServices())
//令牌管理服务,四种模式都需要
.tokenServices(tokenServices())
//密码模式所需要的authenticationManager
.authenticationManager(authenticationManager);
//客户端端配置策略
endpoints.setClientDetailsService(clientDetailsService());
}
/**
* 令牌管理服务的配置
*/
@Bean
public AuthorizationServerTokenServices tokenServices() {
DefaultTokenServices services = new DefaultTokenServices();
//令牌服务,存储策略
services.setTokenStore(tokenStore());
//支持令牌的刷新
services.setSupportRefreshToken(true);
//access_token的过期时间
services.setAccessTokenValiditySeconds(60 * 60 * 2);
//refresh_token的过期时间
services.setRefreshTokenValiditySeconds(60 * 60 * 24 * 3);
return services;
}
/**
* 令牌存储策略
*/
@Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
/**
* 授权码模式的service,使用授权码模式authorization_code必须注入
* 授权码暂时存在内存中,后续可以存储在数据库中
*/
@Bean
public AuthorizationCodeServices authorizationCodeServices() {
return new InMemoryAuthorizationCodeServices();
}
/**
* 客户端模式存储,这里使用内存方式,后续可以存储在数据库
*
* @return
*/
public ClientDetailsService clientDetailsService() {
return new InMemoryClientDetailsService();
}
}
启动测试:
1.授权码模式测试:
授权码模式需要先登录,然后通过同意授权然后获取授权码.
登录验证:
http://localhost:8080/oauth/authorize?client_id=mytest&response_type=code&scope=all&redirect_uri=https://www.baidu.com
同意授权
获取授权码
通过授权码获取token
http://localhost:8080/oauth/token?client_id=myest&client_secret=123456&grant_type=authorization_code&code=cft3A4&redirect_uri=https://www.baidu.com
2.简化模式测试:
简化模式依赖于浏览器的session,所以只能通过浏览器获取token
3.密码模式测试:
密码模式可以直接获取token
http://localhost:8080/oauth/token?client_id=mytest&client_secret=123456&grant_type=password&username=admin&password=111111
4.客户端模式测试:
客户端模式也可以直接获取token
http://localhost:8080/oauth/token?client_id=mytest&client_secret=123456&grant_type=client_credentials
5.刷新Token测试
http://localhost:8080/oauth/token?grant_type=refresh_token&client_id=mytest&client_secret=123456&refresh_token=65c22636-885d-4407-b795-7921a3b73201
6.检查token信息测试
http://localhost:8080/oauth/check_token?token=3ffecdf4-e28b-4ba9-ba1d-62f79ce8b84a
4.4 基于JDBC的认证服务器
pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
</parent>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<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.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<!-- JDBC -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
1.UserDetailsService的实现类
public class MyUserDetailsService implements UserDetailsService {
Map<String, String> user = new HashMap();
Map<String, List<String>> userRole = new HashMap<>();
Map<String,List<String>> roleResources = new HashMap<>();
{
// 用户映射
user.put("admin", "111111");
user.put("user", "111111");
user.put("noaccess", "111111");
//hasRole 的处理逻辑和 hasAuthority 似乎是一样的,只是hasRole 这
// 里会自动给传入的字符串前缀(默认是ROLE_ ),
// 使用 hasAuthority 更具有一致性,不用考虑要不要加 ROLE_ 前缀,
// 在UserDetailsService类的loadUserByUsername中查询权限,也不需要手动增加。
// 在SecurityExpressionRoot 类中hasAuthority 和 hasRole
// 最终都是调用了 hasAnyAuthorityName 方法。
// 用户角色
userRole.put("admin", Arrays.asList("ROLE_admin"));
userRole.put("user", Arrays.asList("query"));
//角色和资源
roleResources.put("/hello",Arrays.asList("admin","query"));
roleResources.put("/helloadmin",Arrays.asList("admin"));
roleResources.put("/hellouser",Arrays.asList("admin","query"));
}
/**
* 通过资源获取资源需要的角色
* @param resources
* @return
*/
public List<String> getRoleByResources(String resources) {
return roleResources.get(resources);
}
@Autowired
PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("MyUserDetailsService.loadUserByUsername 开始");
if (!user.containsKey(username)) {
throw new UsernameNotFoundException("username is not exists");
}
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
if (userRole.containsKey(username)) {
authorities = userRole.get(username).stream().map(row -> new SimpleGrantedAuthority(row))
.collect(Collectors.toList());
}
return new User(username, passwordEncoder.encode(user.get(username)), authorities);
}
}
2.WebSecurityConfigurerAdapter的实现类
@Configuration
@EnableWebSecurity
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
/**
* @return 注:自定义密码编码器,适用于springsecurity和oauth2
*/
@Bean
public BCryptPasswordEncoder getPasswordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* @return
* @throws Exception 注 : 配置认证管理器,springsecurity通过认证管理器来实现认证
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManager();
}
@Bean
MyUserDetailsService myUserDetailsService() {
return new MyUserDetailsService();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//todo 表单登录
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginProcessingUrl("/login")
.permitAll()
.and()
.csrf()
.disable();
http.userDetailsService(myUserDetailsService());
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**");
web.ignoring().antMatchers("/js/**");
web.ignoring().antMatchers("/fonts/**");
}
}
3.AuthorizationServerConfigurerAdapter的验证类
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter {
/**
* 用户认证 Manager
*/
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
DataSource dataSource;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//TODO 可以内存模式也可以存储在数据库中,更加方便
clients.jdbc(dataSource)
.passwordEncoder(new BCryptPasswordEncoder());
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer
//表示支持 client_id 和 client_secret 做登录认证
.allowFormAuthenticationForClients()
//开启/oauth/token_key验证端口权限访问
.tokenKeyAccess("permitAll()")
//开启/oauth/check_token验证端口认证权限访问
.checkTokenAccess("permitAll()");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// 自定义 加载用户信息的接口
endpoints
//允许POST和Get提交访问令牌
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
//授权码模式所需要的authorizationCodeServices
.authorizationCodeServices(authorizationCodeServices())
//令牌管理服务,四种模式都需要
.tokenServices(tokenServices())
//密码模式所需要的authenticationManager
.authenticationManager(authenticationManager);
//客户端端配置策略
endpoints.setClientDetailsService(clientDetailsService());
}
/**
* 令牌管理服务的配置
*/
@Bean
public AuthorizationServerTokenServices tokenServices() {
DefaultTokenServices services = new DefaultTokenServices();
//令牌服务,存储策略
services.setTokenStore(tokenStore());
//支持令牌的刷新
services.setSupportRefreshToken(true);
//access_token的过期时间
services.setAccessTokenValiditySeconds(60 * 60 * 2);
//refresh_token的过期时间
services.setRefreshTokenValiditySeconds(60 * 60 * 24 * 3);
return services;
}
/**
* 令牌存储策略
*/
@Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
/**
* 授权码模式的service,使用授权码模式authorization_code必须注入
* 授权码暂时存在内存中,后续可以存储在数据库中
*/
@Bean
public AuthorizationCodeServices authorizationCodeServices() {
return new JdbcAuthorizationCodeServices(dataSource);
}
/**
* 客户端模式存储,这里使用内存方式,后续可以存储在数据库
*
* @return
*/
public ClientDetailsService clientDetailsService() {
return new JdbcClientDetailsService(dataSource);
}
}
因为是基于DB的所以需要数据库支撑,这里使用Mysql
创建数据表
-- used in tests that use HSQL
create table oauth_client_details (
client_id VARCHAR(255) PRIMARY KEY,
resource_ids VARCHAR(255),
client_secret VARCHAR(255),
scope VARCHAR(255),
authorized_grant_types VARCHAR(255),
web_server_redirect_uri VARCHAR(255),
authorities VARCHAR(255),
access_token_validity INTEGER,
refresh_token_validity INTEGER,
additional_information TEXT(4096),
autoapprove VARCHAR(255)
);
create table oauth_client_token (
token_id VARCHAR(255),
token BLOB,
authentication_id VARCHAR(255) PRIMARY KEY,
user_name VARCHAR(255),
client_id VARCHAR(255)
);
create table oauth_access_token (
token_id VARCHAR(255),
token BLOB,
authentication_id VARCHAR(255) PRIMARY KEY,
user_name VARCHAR(255),
client_id VARCHAR(255),
authentication BLOB,
refresh_token VARCHAR(255)
);
create table oauth_refresh_token (
token_id VARCHAR(255),
token BLOB,
authentication BLOB
);
create table oauth_code (
code VARCHAR(255), authentication BLOB
);
create table oauth_approvals (
userId VARCHAR(255),
clientId VARCHAR(255),
scope VARCHAR(255),
status VARCHAR(10),
expiresAt TIMESTAMP,
lastModifiedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- customized oauth_client_details table
create table ClientDetails (
appId VARCHAR(255) PRIMARY KEY,
resourceIds VARCHAR(255),
appSecret VARCHAR(255),
scope VARCHAR(255),
grantTypes VARCHAR(255),
redirectUrl VARCHAR(255),
authorities VARCHAR(255),
access_token_validity INTEGER,
refresh_token_validity INTEGER,
additionalInformation VARCHAR(4096),
autoApproveScopes VARCHAR(255)
);
INSERT INTO `oauth_client_details` VALUES ('mytest', 'res1', '$10$.zoDUCs9dvnVadGdawloQuwGeaxe/I8vtflZ.dyQQkBaY5C77OXDS', 'all,read,write', 'authorization_code,refresh_token,password', NULL, 'ROLE_TRUSTED_CLIENT', 7200, 7200, NULL, NULL);
数据库连接配置:
spring :
datasource :
username: root
password: root
url: jdbc:mysql://localhost:3306/myboot?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
启动测试和基于内存测试的四种模式一样
5.资源服务器的实现
资源服务器就是需要访问的资源,但是资源被保护了,需要通过认证服务器授权认证之后才能继续访问资源。
pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
</parent>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<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.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
1.新建一个资源Controller
@RestController
public class HelloController {
@GetMapping("/index")
public String index() {
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("<div><a href='/hello'>hello</a></div>");
stringBuffer.append("<div><a href='/helloadmin'>helloadmin</a></div>");
stringBuffer.append("<div><a href='/hellouser'>hellouser</a></div>");
stringBuffer.append("<div><a href='/loginstatus'>loginalert</a></div>");
return stringBuffer.toString();
}
/**
* 未配置权限,只需要登录完成即可访问
* @return
*/
@GetMapping("/loginstatus")
public String loginalert() {
return "已经登录成功了";
}
/**
* 如果动态权限匹配不到,则使用注解
* @return
*/
@GetMapping("/hello")
@PreAuthorize("hasRole('admin') OR hasAuthority('query')")
public String hello() {
return "HelloController hello";
}
@GetMapping("/helloadmin")
@PreAuthorize("hasRole('admin')")
public String helloAdmin() {
return "HelloController helloAdmin";
}
@GetMapping("/hellouser")
@PreAuthorize("hasAuthority('query')")
public String helloUser() {
return "HelloController helloUser";
}
}
2.ResourceServerConfigurerAdapter的实现类
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true,jsr250Enabled = true,securedEnabled = true)
public class ResourceServerConfigurer extends ResourceServerConfigurerAdapter {
/**
* 配置security的安全机制
*/
@Override
public void configure(HttpSecurity http) throws Exception {
//#oauth2.hasScope()校验客户端的权限,这个all是在客户端中的scope
http.authorizeRequests()
.antMatchers("/**").access("#oauth2.hasScope('all')")
.anyRequest().authenticated();
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
//配置唯一资源id
resources.resourceId("res1")
//配置令牌校验服务
.tokenServices(tokenServices());
}
/**
* 配置令牌校验服务,客户端携带令牌访问资源,作为资源端必须检验令牌的真伪
*/
@Bean
public RemoteTokenServices tokenServices() {
//远程调用授权服务的check_token进行令牌的校验
RemoteTokenServices services = new RemoteTokenServices();
// /oauth/check_token 这个url是认证中心校验的token的端点
services.setCheckTokenEndpointUrl("http://localhost:8080/oauth/check_token");
//客户端的唯一id
services.setClientId("mytest");
//客户端的秘钥
services.setClientSecret("123456");
return services;
}
}
配置完成启动测试:
1.使用admin的账号访问三个接口(/hello,/helloadmin,/hellouser)
gettoken:
hello接口:
helloadmin接口:
hellouser接口:
参考:
SpringSecurity + OAuth2 详解 - 江南大才子 - 博客园
Spring Security实现OAuth2.0授权服务 - 进阶版 - 用户不存在! - 博客园
Spring Security + OAuth2.0项目搭建_springsecurity高版本+oauth2 如何搭建-CSDN博客