是一个开放授权标准,
允许第三方应用访问资源拥有者在资源服务器的特定私有资源。
OAuth2中的角色
第三方应用
资源拥有者
资源服务器
授权服务器
维护Client、Resource Owner、Resource Server三者之间的关系。
Resource Server和Authorization Server既可以是分开的两个服务,
也可以是同一个服务。
oauth2定义了四种授权的模式(流程),
oauth2的核心就是向第三方应用授权)
今天我们实现的是用户名密码模式
授权服务器
https://start.spring.io/
Java8
Spring Web、Spring Security、Cloud OAuth2
资源服务器,收到请求后会到auth-server验证
https://start.spring.io/
Java8
Spring Web、Spring Security、Cloud OAuth2
server:
port: 8080
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();
}
}
用户(资源拥有者)身份验证
为了方便演示,把资源拥有者的用户名和密码都写在了代码里,
真实情况应该是从数据库查询。
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);
}
}
}
为了方便演示,把客户端(资源服务器)信息都写在了代码里,
真实情况应该是从数据库查询
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存储方式
}
}
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
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());
}
}
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。
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的值,等会要用到
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搭建统一授权服务