项目结构:
代码地址:
一、什么是OAuth 2.0
OAuth 2.0客服端模式有A、B两台服务器,这里在一个服务中模拟
二、说明
2.1、spring security oauth2建立在spring security基础之上
2.2、
- 配置spring security
- 配置认证服务器
- 配置资源服务器
2.3、oauth2根据使用场景不同,分成了4种模式,还可以自定义模式
2.4、这里主要讲密码模式(以下简称password模式)和客户端模式(以下简称client模式),授权码主要是qq、微信等大平台才会使用到
三、开始整合,看代码
我们的目标是保护资源,控制权限
3.1、创建springboot
3.2、导包
<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>
<version>2.3.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.5.RELEASE</version>
</dependency>
<!-- 将token存储在redis中 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
3.3、创建controller
@RestController
public class HelloController {
/**
* 不需要token
* @param id
* @return
*/
@GetMapping("/product/{id}")
public String getProduct(@PathVariable String id) {
//效验权限
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return "product id : " + id;
}
/**
* 需要token
* @param id
* @return
*/
@GetMapping("/order/{id}")
public String getOrder(@PathVariable String id) {
//效验权限
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return "order id : " + id;
}
}
3.4、配置资源服务器
@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
private static final String DEMO_RESOURCE_ID = "order";
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(DEMO_RESOURCE_ID).stateless(true);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
// 因为我们希望受保护的资源也能在UI中被访问,所以我们需要允许会话创建(在2.0.6中默认禁用)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.requestMatchers().anyRequest()
.and()
.anonymous()
.and()
.authorizeRequests()
// .antMatchers("/product/**").access("#oauth2.hasScope('select') and hasRole('ROLE_USER')")
.antMatchers("/order/**").authenticated();//配置order访问控制,必须认证过后才可以访问
}
}
3.5、配置授权服务器
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
private static final String DEMO_RESOURCE_ID = "order";
@Autowired(required=false)
AuthenticationManager authenticationManager;
@Autowired
RedisConnectionFactory redisConnectionFactory;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//配置两个客户端,client(客户端)认证,password认证
clients.inMemory().withClient("client_1")
.resourceIds(DEMO_RESOURCE_ID)
.authorizedGrantTypes("client_credentials", "refresh_token")
.scopes("select")
.authorities("client")
.secret("123456")
.and().withClient("client_2")
.resourceIds(DEMO_RESOURCE_ID)
.authorizedGrantTypes("password", "refresh_token")
.scopes("select")
.authorities("client")
.secret("123456");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenStore(new RedisTokenStore(redisConnectionFactory))
.authenticationManager(authenticationManager);
}
/**
* 配置:安全检查流程,用来配置令牌端点(Token Endpoint)的安全与权限访问
* 默认过滤器:BasicAuthenticationFilter
* 1、oauth_client_details表中clientSecret字段加密【ClientDetails属性secret】
* 2、CheckEndpoint类的接口 oauth/check_token 无需经过过滤器过滤,默认值:denyAll()
* 对以下的几个端点进行权限配置:
* /oauth/authorize:授权端点
* /oauth/token:令牌端点
* /oauth/confirm_access:用户确认授权提交端点
* /oauth/error:授权服务错误信息端点
* /oauth/check_token:用于资源服务访问的令牌解析端点
* /oauth/token_key:提供公有密匙的端点,如果使用JWT令牌的话
**/
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
//允许表单认证
oauthServer.allowFormAuthenticationForClients();
}
}
3.6、配置spring security(实际项目中,一般数据库保存用户)
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* MyPasswordEncoder类自定义密码匹配规则
* @return
*/
/*@Bean
public static NoOpPasswordEncoder passwordEncoder() {
return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
}*/
@Bean
@Override
protected UserDetailsService userDetailsService(){
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("adminUser").password("adminUser").authorities("adminRole").build());
manager.createUser(User.withUsername("testUser").password("testUser").authorities("testRole").build());
return manager;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.requestMatchers().anyRequest()
.and()
.authorizeRequests()
.antMatchers("/oauth/*").permitAll();
// @formatter:on
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
3.7、自定义密码验证
@Component
public class MyPasswordEncoder implements PasswordEncoder {
@Override
public String encode(CharSequence charSequence) {
return charSequence.toString();
}
/**
* 密码效验
* @param charSequence
* @param s
* @return
*/
@Override
public boolean matches(CharSequence charSequence, String s) {
return s.equals(charSequence.toString());
}
}
3.8、启动程序
四、测试
4.1、password模式:
http://localhost:8080/oauth/token?username=adminUser&password=adminUser&grant_type=password&scope=select&client_id=client_2&client_secret=123456
4.2、client模式:
http://localhost:8080/oauth/token?grant_type=client_credentials&scope=select&client_id=client_1&client_secret=123456
4.3、依次访问以下url
不需要token:
http://localhost:8080/product/1
需要token(访问失败):
http://localhost:8080/order/1
需要token(访问成功):
http://localhost:8080/order/1?access_token=56465b41-429d-436c-ad8d-613d476ff322
初次整合结束
其它入门文档:
2、
Re:从零开始的Spring Security Oauth2(一)
3、
Spring Security 与 OAuth2(完整案例)
4、
Spring Security 解析(五) —— Spring Security Oauth2 开发
5、
Spring Security OAuth2.0分布式认证和授权方案
【精】
异常处理:
1.1、IllegalArgumentException: There is no PasswordEncoder mapped for the id “null”
解决方案:添加 MyPasswordEncoder 类(注意要添加 @Component 注解,启动程序的时候最好用idea原生的,我就在JRebel中吃了亏)
@Component
public class MyPasswordEncoder implements PasswordEncoder {
@Override
public String encode(CharSequence charSequence) {
return charSequence.toString();
}
@Override
public boolean matches(CharSequence charSequence, String s) {
return s.equals(charSequence.toString());
}
}
1.2、{“error”:”unsupported_grant_type”,”error_description”:”Unsupported grant type: password”}
解决方案:意思是没有定义password的模式,需要在 WebSecurityConfigurerAdapter 的实现类中添加以下代码
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
1.3、oauth2验证中WebSecurityConfigurerAdapter和ResourceServerConfigurerAdapter的区别
对于刚入门的来说要好好理解以下以下三个继承类的作用
WebSecurityConfigurerAdapter、ResourceServerConfigurerAdapter、AuthorizationServerConfigurerAdapter
WebSecurityConfigurerAdapter默认情况下是springsecurity的http配置
ResourceServerConfigurerAdapter默认情况下是spring security oauth2的http配置
但是ResourceServerConfigurerAdapter的默认order是大于100的,意思是WebSecurityConfigurerAdapter的拦截顺序要先于ResourceServerConfigurerAdapter,我们想要使用oauth的http配置,就需要在相应的配置文件(yml)配置security.oauth2.resource.filter-order = 3
这样就让ResourceServerConfigurerAdapter的生效