OAuth2
是一个开放授权标准,
允许第三方应用访问资源拥有者在资源服务器的特定私有资源。
角色
OAuth2中的角色
Client
第三方应用
Resource Owner
资源拥有者
Resource Server
资源服务器
Authorization Server
授权服务器
维护Client、Resource Owner、Resource Server三者之间的关系。
Resource Server和Authorization Server既可以是分开的两个服务,
也可以是同一个服务。
四种授权的模式
oauth2定义了四种授权的模式(流程),
oauth2的核心就是向第三方应用授权)
用户名密码模式
我们在微服务之中用到了用户名密码模式来进行鉴权
授权码模式
授权码模式是oauth2中最常用也是最安全的授权模式
隐藏式授权模式
客户端凭证模式
项目说明
今天我们实现的是用户名密码模式
auth-server
授权服务器
Spring Boot2.3.10
https://start.spring.io/
jdk
Java8
Dependencies
Spring Web、Spring Security、Cloud OAuth2
user-server
资源服务器,收到请求后会到auth-server验证
Spring Boot2.3.10
https://start.spring.io/
jdk
Java8
Dependencies
Spring Web、Spring Security、Cloud OAuth2
配置授权服务器
application.yml
server:
port: 8080
Spring Security配置
package com.superv.authserver.config;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
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;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @author yangwei
* @describition Spring Security配置
*
* @time 2021年5月11日-上午10:57:59
*/
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 密码加密工具
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 用户名密码模式要指定的授权管理Bean
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* 允许匿名访问所有接口,否则oauth的/oauth/**接口无法访问
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// TODO Auto-generated method stub
// super.configure(http);
http.authorizeRequests()
.antMatchers("/**").permitAll();
}
}
实现UserDetailsService
用户(资源拥有者)身份验证
为了方便演示,把资源拥有者的用户名和密码都写在了代码里,
真实情况应该是从数据库查询。
package com.superv.authserver.config;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
/**
* @author yangwei
* @describition UserDetailsService实现
*
* @time 2021年5月11日-上午
*/
@Component
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
public PasswordEncoder passwordEncoder;
/**
* 用户身份验证
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// TODO Auto-generated method stub
// return null;
if (!"admin".equalsIgnoreCase(username)) {
throw new UsernameNotFoundException(username);
} else {
// 角色
String role = "ROLE_ADMIN";
List<SimpleGrantedAuthority> authorities = new ArrayList<SimpleGrantedAuthority>();
authorities.add(new SimpleGrantedAuthority(role));
String password = passwordEncoder.encode("123456");
return new org.springframework.security.core.userdetails.User(username, password, authorities);
}
}
}
Cloud OAuth2配置
为了方便演示,把客户端(资源服务器)信息都写在了代码里,
真实情况应该是从数据库查询
package com.superv.authserver.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.crypto.password.PasswordEncoder;
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.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
/**
* @author yangwei
* @describition Cloud OAuth2配置
*
* @time 2021年5月11日-上午
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
public TokenStore tokenStore;
@Autowired
public PasswordEncoder passwordEncoder;
@Autowired
public UserDetailsService userDetailsService;
@Autowired
public AuthenticationManager authenticationManager;
/**
* 基于内存的TokenStore
* @return
*/
@Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
// TODO Auto-generated method stub
// super.configure(security);
security.allowFormAuthenticationForClients(); // 允许客户端即资源服务器访问oauth2授权接口
security.tokenKeyAccess("isAuthenticated()"); // 允许已授权用户访问获取token接口
security.checkTokenAccess("isAuthenticated()"); // 允许已授权用户访问验证token接口
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// TODO Auto-generated method stub
// super.configure(clients);
clients.inMemory()
.withClient("user-client") // 资源服务器
.secret(passwordEncoder.encode("user-secret-8888"))
.authorizedGrantTypes("refresh_token", "authorization_code", "password") // 用户名密码模式
.accessTokenValiditySeconds(3600) // token有效时间:秒
.scopes("all");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// TODO Auto-generated method stub
// super.configure(endpoints);
endpoints.authenticationManager(authenticationManager) // 支持用户名密码模式
.userDetailsService(userDetailsService) // 用户验证服务
.tokenStore(tokenStore); // token存储方式
}
}
配置资源服务器
application.yml
server:
port: 8081
security:
oauth2:
client:
client-id: user-client
client-secret: user-secret-8888
# access-token-uri: http://localhost:8080/oauth/token
# user-authorization-uri: http://localhost:8080/oauth/authorize
resource:
id: user-client
user-info-uri: user-info
authorization:
check-token-access: http://localhost:8080/oauth/check_token
ResourceServer配置
package com.superv.userserver.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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;
/**
* @author yangwei
* @describition ResourceServer配置
*
* @time 2021年5月11日-下午5:01:27
*/
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Value("${security.oauth2.client.client-id}")
private String clientId;
@Value("${security.oauth2.client.client-secret}")
private String clientSecret;
@Value("${security.oauth2.authorization.check-token-access}")
private String checkTokenEndpointUrl;
@Bean
public RemoteTokenServices tokenService() {
RemoteTokenServices tokenService = new RemoteTokenServices();
tokenService.setClientId(clientId);
tokenService.setClientSecret(clientSecret);
tokenService.setCheckTokenEndpointUrl(checkTokenEndpointUrl);
return tokenService;
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
// TODO Auto-generated method stub
// super.configure(resources);
resources.tokenServices(tokenService());
}
}
资源:controller
package com.superv.userserver.controller;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/hello")
@PreAuthorize("hasAnyRole('ROLE_ADMIN')")
public String hello() {
return "hello";
}
}
验证
分别启动授权服务器auth-server、资源服务器user-server,postman。
请求授权服务器获取access_token
POST:http://localhost:8080/oauth/token?grant_type=password&username=admin&password=123456&scope=all
请求header中添加Authorization,
值为:Basic dXNlci1jbGllbnQ6dXNlci1zZWNyZXQtODg4OA==
注意:Basic后跟有一个空格
Basic+" "后的内容为"user-client:user-secret-8888"经过base64编码后的值
access_token的值,等会要用到
携带access_token访问资源服务器
GET:http://localhost:8081/user/hello
请求header中添加Authorization,
值为:bearer 1e48fb5d-ed96-467b-a592-b8a449d9d128
注意:bearer后跟有一个空格,bearer是固定写法为token的类型
bearer+" "后的内容为上一步获取到的access_token
返回结果正常:hello
过程
如果不携带access_token访问会怎么样?你会看到这样的结果:
{
"error": "unauthorized",
"error_description": "Full authentication is required to access this resource"
}
这中间又发生了哪些不为人知的事情呢?
当我们通过客户端-postman访问资源服务器时,
OAuth2AuthenticationProcessingFilter拦截token,
我也不知道对应图中的哪一个。。
然后会调用RemoteTokenServices,
去授权服务器http://localhost:8080/oauth/check_token验证token,
把无状态的token转化成用户信息。
不当之处,请予指正。
参考文章:
架构师girl:可能是全网最详细的 Spring Cloud OAuth2 单点登录使用教程
MacroZheng:Spring Cloud Security:Oauth2使用入门
LightOfMiracle:Spring Boot整合oauth2.0搭建统一授权服务