什么是 oAuth
oAuth 协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是 oAuth 的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此 oAuth 是安全的。
什么是 Spring Security
Spring Security 是一个安全框架,前身是 Acegi Security,能够为 Spring 企业应用系统提供声明式的安全访问控制。Spring Security 基于 Servlet 过滤器、IOC 和 AOP,为 Web 请求和方法调用提供身份确认和授权处理,避免了代码耦合,减少了大量重复代码工作。
客户端授权模式
客户端必须得到用户的授权(authorization grant),才能获得令牌(access token)。oAuth 2.0 定义了四种授权方式。
- implicit:简化模式,不推荐使用
- authorization code:授权码模式(本文章采用这种模式)
- resource owner password credentials:密码模式
- client credentials:客户端模式
个人对oAuth2认证过程的理解
编码部分
- 创建项目工程,本文章采用多module方式
主要是配置一个pom,文章结束后代码会发到github,这里不复制了 - 创建统一依赖
统一依赖中配置org.springframework.cloud
springboot2.1.x需要使用Greenwich版本 - 创建认证服务器模块(内存模拟版本)
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
// 注入 WebSecurityConfiguration 中配置的 BCryptPasswordEncoder
@Autowired
private BCryptPasswordEncoder passwordEncoder;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 配置客户端
clients
// 使用内存设置
.inMemory()
// client_id
.withClient("client")
// client_secret
.secret(passwordEncoder.encode("secret"))
// 授权类型
.authorizedGrantTypes("authorization_code")
// 授权范围
.scopes("app")
// 注册回调地址
.redirectUris("http://blog.yhhu.xyz");
}
}
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Bean
public BCryptPasswordEncoder passwordEncoder() {
// 设置默认的加密方式
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
// 在内存中创建用户并为密码加密
.withUser("user").password(passwordEncoder().encode("123456")).roles("USER")
.and()
.withUser("admin").password(passwordEncoder().encode("123456")).roles("ADMIN");
}
}
SpringSecurity提供默认的登录界面,并且提供RestfulAPI
spring:
application:
name: oauth2-server
server:
port: 8080
==必须遵循这样的请求url==
获取code:在浏览器直接访问http://localhost:8080/oauth/authorize?client_id=client&response_type=code
通过code获取access_token:http://client:secret@localhost:8080/oauth/token
这里需要带两个参数,采用x-www-form-urlencoded方式,grant_type为authorization_code,code为上一步获取到的内容
url说明:client:secret不是固定的,这取决于你在AuthorizationServerConfiguration中withClient和secret的设置
- 创建认证服务器模块(JDBC版本)
- 创建一个oauth数据库,官方给的表示H2的,mysql有点改动,百度找了一个
CREATE TABLE `clientdetails` ( `appId` varchar(128) NOT NULL, `resourceIds` varchar(256) DEFAULT NULL, `appSecret` varchar(256) DEFAULT NULL, `scope` varchar(256) DEFAULT NULL, `grantTypes` varchar(256) DEFAULT NULL, `redirectUrl` varchar(256) DEFAULT NULL, `authorities` varchar(256) DEFAULT NULL, `access_token_validity` int(11) DEFAULT NULL, `refresh_token_validity` int(11) DEFAULT NULL, `additionalInformation` varchar(4096) DEFAULT NULL, `autoApproveScopes` varchar(256) DEFAULT NULL, PRIMARY KEY (`appId`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `oauth_access_token` ( `token_id` varchar(256) DEFAULT NULL, `token` blob, `authentication_id` varchar(128) NOT NULL, `user_name` varchar(256) DEFAULT NULL, `client_id` varchar(256) DEFAULT NULL, `authentication` blob, `refresh_token` varchar(256) DEFAULT NULL, PRIMARY KEY (`authentication_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `oauth_approvals` ( `userId` varchar(256) DEFAULT NULL, `clientId` varchar(256) DEFAULT NULL, `scope` varchar(256) DEFAULT NULL, `status` varchar(10) DEFAULT NULL, `expiresAt` timestamp NULL DEFAULT NULL, `lastModifiedAt` timestamp NULL DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `oauth_client_details` ( `client_id` varchar(128) NOT NULL, `resource_ids` varchar(256) DEFAULT NULL, `client_secret` varchar(256) DEFAULT NULL, `scope` varchar(256) DEFAULT NULL, `authorized_grant_types` varchar(256) DEFAULT NULL, `web_server_redirect_uri` varchar(256) DEFAULT NULL, `authorities` varchar(256) DEFAULT NULL, `access_token_validity` int(11) DEFAULT NULL, `refresh_token_validity` int(11) DEFAULT NULL, `additional_information` varchar(4096) DEFAULT NULL, `autoapprove` varchar(256) DEFAULT NULL, PRIMARY KEY (`client_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `oauth_client_token` ( `token_id` varchar(256) DEFAULT NULL, `token` blob, `authentication_id` varchar(128) NOT NULL, `user_name` varchar(256) DEFAULT NULL, `client_id` varchar(256) DEFAULT NULL, PRIMARY KEY (`authentication_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `oauth_code` ( `code` varchar(256) DEFAULT NULL, `authentication` blob ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `oauth_refresh_token` ( `token_id` varchar(256) DEFAULT NULL, `token` blob, `authentication` blob ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
- 操作一下oauth_client_details表
需要设置的内容为client_id,client_secret,scope,authorized_grant_types,web_server_redirect_uri,注意client_secret是经过加密的
至此完成验证功能,其他操作和内存版都是一样的,完成后可以在数据库中看到看到token相关信息
RBAC(Role-Based Access Control,基于角色的访问控制)
增加了几个表,模型如下
可以做的简单一点,可是这样做功能更加完备,单单在user表中插入role列的话不便于管理
下面开始具体实现
- 在认证服务器端排除token检查的权限判断
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/oauth/check_token");
}
创建一个service,查询用户使用(代码略)
将授权模式由内存模式改为userDetailService方式
@Autowired
private DataSource dataSource;
@Bean
public TokenStore tokenStore() {
// 基于 JDBC 实现,令牌保存到数据
return new JdbcTokenStore(dataSource);
}
@Bean
public ClientDetailsService jdbcClientDetails() {
// 基于 JDBC 实现,需要事先在数据库配置客户端信息
return new JdbcClientDetailsService(dataSource);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception
{
// 设置令牌
endpoints.tokenStore(tokenStore());
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService());
}
- 认证中心UserDetail权限判断逻辑
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private TbUserService tbUserService;
@Autowired
private TbPermissionService tbPermissionService;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
TbUser tbUser = tbUserService.getByUsername(s);
List<GrantedAuthority> grantedAuthorities= Lists.newArrayList();
if (tbUser != null) {
List<TbPermission> tbPermissions = tbPermissionService.selectByUserId(tbUser.getId());
tbPermissions.forEach(item -> {
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(item.getEnname());
grantedAuthorities.add(grantedAuthority);
});
return new User(tbUser.getUsername(),tbUser.getPassword(),grantedAuthorities);
}
return null;
}
}
- 如何自定义一个权限控制方法?(通过permission中url对应的方式来判断权限)
定义权限判断类
@Component("permission")
public class PermissionCheck {
private AntPathMatcher antPathMatcher = new AntPathMatcher();
public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
boolean hasPermission = false;
//取出当前token对应用户所拥有的权限
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (GrantedAuthority authority : authorities) {
if (antPathMatcher.match(authority.getAuthority(), request.getRequestURI())) {
hasPermission = true;
break;
}
}
return hasPermission;
}
}
在资源服务器中配置access("")方法来调用hasPermission方法
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.exceptionHandling()
.and()
//管理session的创建策略永远不会创建HttpSession,它不会使用HttpSession来获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
//ALWAYS总是创建HttpSession
//IF_REQUIREDSpring Security只会在需要时创建一个HttpSession
//NEVERSpring Security不会创建HttpSession,但如果它已经存在,将可以使用HttpSession
.and()
.authorizeRequests()
.anyRequest().access("@permission.hasPermission(request,authentication)");
// 以下为配置所需保护的资源路径及权限,需要与认证服务器配置的授权部分对应
// .antMatchers("/**").hasAuthority("SystemContent");
}
}
补充一个错误:Spring security oauth2 throwing "no bean resolver registered" for custom bean
在ResourceServerConfiguration类下补充以下配置
/**
* 以下为采坑地方,如果不使用自己的动态权限控制,下面无需配置也能运行
* 原因暂时不明,解决方案为参考以下issues地址
* https://github.com/spring-projects/spring-security-oauth/issues/730#issuecomment-219480394
*/
@Autowired
private OAuth2WebSecurityExpressionHandler expressionHandler;
@Bean
public OAuth2WebSecurityExpressionHandler oAuth2WebSecurityExpressionHandler(ApplicationContext applicationContext) {
OAuth2WebSecurityExpressionHandler expressionHandler = new OAuth2WebSecurityExpressionHandler();
expressionHandler.setApplicationContext(applicationContext);
return expressionHandler;
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.expressionHandler(expressionHandler);
}
完整代码地址
github:oauth2
Spring Security OAuth2学习的更多相关文章
-
Spring Cloud 学习 (十) Spring Security, OAuth2, JWT
通过 Spring Security + OAuth2 认证和鉴权,每次请求都需要经过 OAuth Server 验证当前 token 的合法性,并且需要查询该 token 对应的用户权限,在高并发场 ...
-
【OAuth2.0】Spring Security OAuth2.0篇之初识
不吐不快 因为项目需求开始接触OAuth2.0授权协议.断断续续接触了有两周左右的时间.不得不吐槽的,依然是自己的学习习惯问题,总是着急想了解一切,习惯性地钻牛角尖去理解小的细节,而不是从宏观上去掌握 ...
-
Spring Security Oauth2系列(一)
前言: 关于oauth2,其实是一个规范,本文重点讲解spring对他进行的实现,如果你还不清楚授权服务器,资源服务器,认证授权等基础概念,可以移步理解OAuth 2.0 - 阮一峰,这是一篇对于oa ...
-
spring security oauth2 jwt 认证和资源分离的配置文件(java类配置版)
最近再学习spring security oauth2.下载了官方的例子sparklr2和tonr2进行学习.但是例子里包含的东西太多,不知道最简单最主要的配置有哪些.所以决定自己尝试搭建简单版本的例 ...
-
Spring Security OAuth2 SSO
通常公司肯定不止一个系统,每个系统都需要进行认证和权限控制,不可能每个每个系统都自己去写,这个时候需要把登录单独提出来 登录和授权是统一的 业务系统该怎么写还怎么写 最近学习了一下Spring Sec ...
-
Spring Security Oauth2 的配置
使用oauth2保护你的应用,可以分为简易的分为三个步骤 配置资源服务器 配置认证服务器 配置spring security 前两点是oauth2的主体内容,但前面我已经描述过了,spring sec ...
-
Re:从零开始的Spring Security Oauth2(一)
前言 今天来聊聊一个接口对接的场景,A厂家有一套HTTP接口需要提供给B厂家使用,由于是外网环境,所以需要有一套安全机制保障,这个时候oauth2就可以作为一个方案. 关于oauth2,其实是一个规范 ...
-
Spring Security OAuth2 Demo —— 授权码模式
本文可以转载,但请注明出处https://www.cnblogs.com/hellxz/p/oauth2_oauthcode_pattern.html 写在前边 在文章OAuth 2.0 概念及授权流 ...
-
Spring Security 解析(七) —— Spring Security Oauth2 源码解析
Spring Security 解析(七) -- Spring Security Oauth2 源码解析 在学习Spring Cloud 时,遇到了授权服务oauth 相关内容时,总是一知半解,因 ...
随机推荐
-
POJ2104 K-th Number Range Tree
又是区间第k大,这次选择这道题是为以后写线段树套平衡树铺路的.Range Tree可以理解成线段树套vector吧,相当于每个结点多存了对应区间的一个排好序的序列.画一下就会知道空间的消耗是nlogn ...
-
iBatis之type
iBatis下关于type的UML图,展示iBatis下关于类型的处理和注册等.
-
CentOS7桌面版系统使用的一些小技巧
1. 清空~/.kde/ 文件下的文件,登陆后不显示桌面的解决方法 在使用CentOS7 桌面系统时,有时候打开文件会很卡.这时我们需要清空当前用户下的 .kde 文件下的所有文件. 再重新登陆该用户 ...
-
学习笔记之机器学习(Machine Learning)
机器学习 - *,*的百科全书 https://zh.wikipedia.org/wiki/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0 机器学习是人工智能的一个分 ...
-
20155230 2016-2017-2 《Java程序设计》第九周学习总结
20155230 2016-2017-2 <Java程序设计>第九周学习总结 教材学习内容总结 第十六章 statement在不使用时所关联的resultset也会自动关闭. 要让SQL执 ...
-
C/C++语言中指针数组和数组指针比较区别
指针数组:int *p[3] 定义一个指针数组,其中每个数组元素指向一个int型变量的地址 意思就是指针的数组,数组里面都是指针 例子: int *p[3];//定义了一个指针数组,有3个成员,每个成 ...
-
Locality preserving hashing for fast image search: theory and applications
Is there any Java library that provides an implementation (or several) of a Locality Preserving Hash ...
-
Ubuntu 16.04 安装 IDEA
1.下载地址:https://www.jetbrains.com/idea/download/#section=linux 选择without jdk版本下载 2.下载完成 解压 到 /opt下 先却 ...
-
cdoj 1256 昊昊爱运动 预处理
昊昊爱运动 Time Limit: 3000/1000MS (Java/Others) Memory Limit: 65535/65535KB (Java/Others) 昊昊喜欢运动 他NN ...
-
JAVA实现DIJKSTRA算法
import java.util.Queue; import java.util.LinkedList; public class dijkstra{ public static void main( ...