先贴一个Spring-security的官方文档地址:
学习一门技术,最好的方法就是去看他的官方文档。
简介:
OAuth2(开放授权)是一个开放标准,允许用户授权第三方应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方移动应用或分享他们数据的所有内容。
简而言之,Oauth2就是经过一次验证,将客户端及个人信息保存在服务器上,当在第三方应用上需要认证或者授权时,无需再次登录认证。
这是一个简单的流程图,便于大家快速了解Oauth2的认证流程:
接下来,我会按照下面几个模块,来介绍一下Oauth2的相关知识:
- 相关概念
- Oauth2客户端授权方式
- 部分源码解读
- 答疑解惑
相关概念
-
令牌:一个代表特定作用域、生命期以及其他访问属性的字符串。用以代替使用资源所有者的凭据来访问受保护资源。通俗来讲,就是用户认证之后,Oauth2返回的access_token字符串值,这个值包含了上述所说的属性,用以颁发给第三方应用。
-
客户端:使用资源所有者的授权代表资源所有者发起对受保护资源的请求的应用程序。通俗的理解,就是用户所要访问的资源。
-
授权服务器:负责生成令牌(access_token)等信息。
-
资源服务器:存放资源所有者提供的资源信息,校验令牌信息以及其他事务处理等。
备注:授权服务器可以和资源服务器是同一台服务器,也可以是分离的个体,从而形成一对多的关系。
Before Work
在此之前,大致讲一下相关配置。demo地址我随后贴出。
授权服务器配置
在此配置中,我们可以配置客户端/用户处理接口,TokenStore,Oauth2端点信息等。
-
两个比较重要的接口:UserDetailsService和ClientDetailsService。
a.UserDetailsService,此接口用于给oauth返回用户信息,实际中需要自己扩展该接口,用于数据校验。下面是一段伪代码
@Service("userDetailService") @AllArgsConstructor @Slf4j public class EcUserDetailServiceImpl implements EcUserDetailsService { /** * 用户密码登录 * * @param username 用户名 */ @Override @SneakyThrows public UserDetails loadUserByUsername(String username) { HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())) .getRequest(); final String userAccount = getUserAccount(map); if (StrUtil.isBlank(userAccount)) { throw new CustomException("用户不存在,请先注册!"); } String username = userAccount; List<CbUser> cbUsers = userService.selectCbUserList(CbUser.builder().userName(username).build()); if (CollectionUtil.isEmpty(cbUsers)) { throw new CustomException("用户不存在,请先注册!"); } CbUser user = cbUsers.get(0); // 如果不在其它地方修改设置密码,那么取用数据库中的密码 return new MyUser( user.getUserName(), StrUtil.blankToDefault(getPassword(), user.getPassword()), additionalInfo, getGrantedAuthorities(StrUtil.utf8Str(user.getId()))); } }
b.ClientDetailsService oauth2获取客户端信息接口,实现有JdbcClientDetailsService和InMemoryClientDetailsService,顾名思义,是分别从db和内存中获取数据。配置文件中,我们配置了db的方式,实际情况下,也是将client持久化到db,方便维护。实现类提供了客户端的crud方法,如果没有定制需求,框架默认的实现是能够满足需求的。
-
tokenStore,即Oauth2令牌持久化接口,可以通过jdbc,redis,memory,jwt来实现。我的demo通过mysql来持久化token信息。
在官方github上,有默认的初始化sql,如果不需要特殊改造,直接用即可。
-
oauth_client_details:客户端表,维护客户端信息,诸如客户端访问权限、access_token有效期、refresh_token有效期等,具体见sql定义,这里不做过多描述。该表可以使用JdbcClientDetailsService类来进行操作。
-
oauth_access_token:令牌表,维护token信息。最重要的表,表结构如下:
字段名 字段说明 create_time 数据的创建时间,精确到秒,由数据库在插入数据时取当前系统时间自动生成(扩展字段) token_id 该字段的值是将access_token的值通过MD5加密后存储的. token 存储将OAuth2AccessToken.java对象序列化后的二进制数据, 是真实的AccessToken的数据值. authentication_id 该字段具有唯一性, 其值是根据当前的username(如果有),client_id与scope通过MD5加密生成的. 具体实现请参DefaultAuthenticationKeyGenerator.java类. user_name 登录时的用户名, 若客户端没有用户名(如grant_type=“client_credentials”),则该值等于client_id client_id authentication 存储将OAuth2Authentication.java对象序列化后的二进制数据 refresh_token 该字段的值是将refresh_token的值通过MD5加密后存储的. 在项目中,主要操作oauth_access_token表的对象是JdbcTokenStore.java. 更多的细节请参考该类 authentication字段保存的是包含客户端和用户信息的OAuth2Authentication认证对象。
-
oauth_refresh_token: 令牌刷新表。
用户表和权限相关表,表结构大佬直接贴在注释上了。实际开发中,这些都需要自己扩展,满足项目要求,覆盖框架本身的校验逻辑等。
资源服务器配置
使用@EnableResourceServer注解来标识资源服务器,资源服务器可以和授权服务器在一台主机上,也可以分开,按实际情况来配置。
spring security配置
Java在正常servelet处理http的请求可能会经过很多的filter,spring security自己有一个叫FilterChainProxy代理类,其维护了一个List,每个chain里面有若干个filter,spring security里一个请求只会被一个filter chain进行处理,只要找到能处理该请求的filter chain就不再进行其他的filter chain匹配。
Security默认会为我们创建一些filter,想要了解各个filter的作用以及执行顺序,参考官方文档:
或者org.springframework.security.config.annotation.web.builders.FilterComparator类
或者
ResourceServerConfigurerAdapter和WebSecurityConfigurerAdapter都对HttpSecurity进行了配置,他们所属的功能模块不同,前者是spring security oauth2里的,后者是spring security的。他们都是Adapter,他们都会产生一个filter Chain,两者可以相互配合来对不同的Url进行权限控制。默认情况下,WebSecurityConfigureAdapter的@Order值为100,ResourceServerConfigurerAdapter 的@Order值为3,在spring体系里,order值越小,优先级越高,所以资源服务器会优先处理请求,而WebSecurityConfigurerAdapter会失效。
Oauth2客户端授权类型
四种类型分别是:
- 简化模式(implicit)
- 授权码模式(authorization-code)
- 密码模式(password)
- 客户端模式(client credentials)
简化模式
简化模式适用于纯静态页面应用。所谓纯静态页面应用,也就是应用没有在服务器上执行代码的权限(通常是把代码托管在别人的服务器上),只有前端 JS 代码的控制权。
这种场景下,应用是没有持久化存储的能力的。因此,按照 oAuth2.0 的规定,这种应用是拿不到 Refresh Token 的。该模式下,access_token
容易泄露且不可刷新,所以现在几乎不用,了解即可。
答疑解惑
WebSecurity和HttpSecurity的关系
一WebSecurity用来生成FilterChainProxy,而HttpSecurity则用来生成FilterChainProxy中的SecurityFilterChain,SecurityFilterChain定义了uri规则和过滤器集合。
public class FilterChainProxy extends GenericFilterBean {
##########################
private List<SecurityFilterChain> filterChains;
##########################
}
public interface SecurityFilterChain {
boolean matches(HttpServletRequest request);
List<Filter> getFilters();
}
1.http://localhost:8080/oauth/authorize?response_type=token&client_id=test-app&redirect_uri=http://baidu.com
2.http://localhost:8080/oauth/authorize?response_type=code&client_id=test-app
参考:
- https://www.cnblogs.com/storml/p/10930121.html
- https://docs.spring.io/spring-security/site/docs/5.0.6.RELEASE/reference/htmlsingle
真的一滴也没有了