一、Oauth2
1.1、Oauth2
OAuth(Open Authorization,开放授权)是为用户资源的授权定义了一个安全、开放及简单的标准,第三方无需知道用户的账号及密码,就可获取到用户的授权信息。即允许用户授权第三方应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方应用或分享他们数据的所有内容。
OAuth2.0是OAuth协议的延续版本,但不向后兼容OAuth 1.0即完全废止了OAuth1.0。
1.2、OAuth 2.0主要角色
①客户应用 (Client Application) :通常是一个Web或者无线应用, 它需要访问用户的受保护资源
②资源服务器 (Resource Server) :是一个web站点或者web service API,用户的受保护数据保存于此
③授权服务器 (Authorized Server) :在客户应用成功认证并获得授权之后,向客户应用颁发访问令牌 Access Token
④资源拥有者 (Resource Owner) :资源的拥有人, 想要分享某些资源给第三方应用
Oauth2角色详细分为如下:
①Third-party application:第三方应用程序,本文中又称"客户端"(client)
②HTTP service:HTTP服务提供商,本文中简称"服务提供商"
a、Authorization server:认证服务器,即服务提供商专门用来处理认证的服务器。
b、Resource server:资源服务器,即服务提供商存放用户生成的资源的服务器。它与认证服务器,可以是同一台服务器,也可以是不同的服务器。
③Resource Owner:资源所有者,本文中又称"用户"(user)。
④User Agent:用户代理,本文中就是指浏览器。
1.3、OAuth 2.0授权类型(Flows)
OAuth2.0 定义了 四种授权模式:
①授权码Authorization Code:授权码模式,结合普通服务器端应用使用。
②简化Implicit:简化模式,结合移动应用或 Web App 使用。
③用户名密码Resource Owner Credentials:密码模式,适用于受信任客户端应用,例如同个组织的内部或外部应用。
④客户端凭证Client Credentials:客户端模式,适用于客户端调用主服务API型应用(比如百度API Store)
1.4、OAuth2 授权流程
获取微信用户信息示例来说
①微信用户a,访问第三方应用分享到微信中的活动页面,第三方应用即向微信授权服务器 发起授权请求以获取该微信用户a在微信服务器上的姓名,头像等基本信息(私有资源)
②微信授权服务器接收到第三方应用的授权请求(包含第三方应用的回调地址的),并引导用户确认授权(也可以选择用户静默授权)后,返回授权许可(code)给到第三方应用(根据授权请求传入的回调地址)
③第三方应用拿到授权许可code后,再次向微信授权服务器发起访问令牌的请求(携带身份app_id等)
④微信授权服务器验证第三方应用的身份以及授权许可code,验证通过后将下发访问令牌access_code,此外还有刷新令牌以及令牌过期时间等信息给到第三方应用
⑤第三方应用拿到访问令牌后向微信资源服务器发起请求资源,即请求微信用户a的姓名,头像,地域等基本信息
⑥微信资源服务器根据访问令牌,返回微信用户a的基本信息给到第三方应用。
1.5、授权服务
授权服务器的工作由资源服务器提供,主要提供两类接口。
①获取授权接口:接受第三方应用的授权请求,引导用户到资源服务器完成登陆授权的过程
②获取访问令牌接口:使用授权接口提供的许可凭证来颁发用户的访问令牌给到第三方应用,或者又第三方应用更新过期的访问令牌。
即
①AuthorizationEndpoint:用来作为请求者获得授权的服务,默认的URL是/oauth/authorize.
②TokenEndpoint:用来作为请求者获得令牌(Token)的服务,默认的URL是/oauth/token.
二、springboot2集成oauth2
2.1、创建SpringBoot项目
开发工具:idea2019.3
Jdk版本:java1.8
调试工具:postman
目录结构:一个认证服务oauth、一个资源服务resource
oauth有登录和用户查询相关接口,resource没有登录接口,只有业务接口
2.2、springboot2集成oauth2
Oauth2实例可以分为简易的分为三个步骤:配置资源服务器、配置认证服务器、配置spring security。
pom文件添加oauth2依赖如下
<!-- 引入security依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- 引入oauth2 依赖-->
<!-- 由于一些注解和API从spring security5.0中移除,所以需要导入下面的依赖包 -->
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
2.2.1、配置资源服务器
@Slf4j
@Configuration
@EnableResourceServer
public class Oauth2ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Autowired
private CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
@Autowired
private CustomAccessDeniedHandler customAccessDeniedHandler;
@Autowired
private RoleMapper roleMapper;
@Autowired
private PermissionMapper permissionMapper;
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("oauth2-resource1").stateless(true);
//若认证和资源分开则使用下面
/*RemoteTokenServices tokenService = new RemoteTokenServices();
tokenService.setCheckTokenEndpointUrl("http://localhost:8080/oauth/token");
tokenService.setClientId("client2");
tokenService.setClientSecret("1");
resources.tokenServices(tokenService);*/
}
@Override
public void configure(HttpSecurity http) throws Exception {
log.info("请求资源服务-----------------------------------------------------");
http
.exceptionHandling()
.authenticationEntryPoint(customAuthenticationEntryPoint)
.accessDeniedHandler(customAccessDeniedHandler)
.and()
//requestMatchers放在最前面,对需要匹配的的规则进行自定义与过滤
.requestMatchers().antMatchers("/oauth2/**", "/user/**")
.and()
.authorizeRequests().antMatchers("/oauth2/logout","/user/getUserList").authenticated();
//其余接口没有角色限制,但需要经过认证,只要携带token就可以放行
//.anyRequest()
//.authenticated();
//动态加载数据库中角色权限(接口和角色动态调整),在SpringSecurity校验权限的时候,会自动将权限前面加ROLE_,所以我们需要 将我们数据库中配置的ROLE_截取掉。
List<Role> roleList = roleMapper.getRoleList();
for (Role role : roleList) {
List<Permission> permissionList = permissionMapper.getPermissionListByRoleId(role.getId());
for (Permission permission : permissionList) {
http.authorizeRequests()
.antMatchers(permission.getUrl())
//自动加ROLE_
.hasRole(role.getName());
}
}
}
}
2.2.2、配置认证服务器
@Slf4j
@Configuration
@EnableAuthorizationServer
public class Oauth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Resource
private AuthenticationManager authenticationManager;
@Resource
private RedisConnectionFactory redisConnectionFactory;
@Resource
private DataSource dataSource;
@Resource
private CustomClientDetailsService customClientDetailsService;
/**
* 授权码模式code存储方式,默认内存存储
* @return
*/
@Bean
public AuthorizationCodeServices authorizationCodeServices() {
return new JdbcAuthorizationCodeServices(dataSource);
}
/**
* 授权允许存储方式,默认内存存储
* @return
*/
@Bean
public ApprovalStore approvalStore() {
return new JdbcApprovalStore(dataSource);
}
/**
* oauth_client_token操作这个数据库表,每个需要中继的节点都需要建立这个表
* OAuth 2 客户端 client_credentials授权方式获得的Token
* @return
*/
@Bean
public JdbcClientTokenServices jdbcClientTokenServices() {
return new JdbcClientTokenServices(dataSource);
}
/**
* token存储在redis,可以存储在内存中,关系型数据库中,redis中
* 注意:如果不保存access_token,则没法通过access_token取得用户信息
* @return
*/
@Bean
public TokenStore tokenStore() {
//return new InMemoryTokenStore();
//return new JdbcTokenStore(dataSource);
return new RedisTokenStore(redisConnectionFactory);
}
/**
* @description 用来配置客户端详情服务(ClientDetailsService),客户端详情信息在这里初始化
* 可以把客户端详情信息写死也可以写入内存或者数据库中
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
log.info("初始化客户端详情------------------------------------------------");
//使用自定义客户ClientDetailsService初始化配置
clients.withClientDetails(customClientDetailsService);
}
/**
* 用来配置令牌端点(Token Endpoint)的安全约束
* @param security
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
// 自定义异常处理端口
security.authenticationEntryPoint(new CustomAuthenticationEntryPoint());
security
// code授权添加
.realm("oauth2-resources")
// 所有人可访问 /oauth/token_key 后面要获取公钥, 默认拒绝访问
.tokenKeyAccess("permitAll()")
// 认证后可访问 /oauth/check_token,默认拒绝访问, "isAuthenticated()"
.checkTokenAccess("permitAll()")
// 支持把secret和clientId写在url上,否则需要在头上
.allowFormAuthenticationForClients();
}
/**
* @description token及用户信息存储到redis,当然你也可以存储在当前的服务内存,不推荐
* 用来配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services),还有token的存储方式(tokenStore)
* @param endpoints 访问端点
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
//token信息存到redis,通过authenticationManager开启密码模式授权
endpoints.tokenStore(tokenStore()).authenticationManager(authenticationManager);
//配置TokenService参数
DefaultTokenServices tokenServices = new DefaultTokenServices();
//token持久化容器
tokenServices.setTokenStore(endpoints.getTokenStore());
//是否支持refresh_token,默认false
tokenServices.setSupportRefreshToken(true);
//客户端信息
tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
//自定义token生成
tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
//access_token 的有效时长 (秒), 默认 12 小时;1小时
tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.HOURS.toSeconds(1));
//refresh_token 的有效时长 (秒), 默认 30 天;1小时
tokenServices.setRefreshTokenValiditySeconds((int) TimeUnit.HOURS.toSeconds(1));
//是否复用refresh_token,默认为true(如果为false,则每次请求刷新都会删除旧的refresh_token,创建新的refresh_token)
tokenServices.setReuseRefreshToken(false);
//token相关服务
endpoints.tokenServices(tokenServices);
endpoints
// jdbc存储方式
//.tokenStore(tokenStore())
// 授权允许存储方式
.approvalStore(approvalStore())
// 授权码模式code存储方式
.authorizationCodeServices(authorizationCodeServices());
}
}
2.2.3、配置spring security
@Order(1)
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class Oauth2WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private CustomUserDetailsService customUserDetailsService;
@Resource
private CustomAuthenticationProvider customAuthenticationProvider;
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 配置登录验证
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//自定义用户userDetailService、加密
auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder());
//加入自定义的安全认证
auth.authenticationProvider(customAuthenticationProvider);
}
@Override
public void configure(HttpSecurity http) throws Exception {
//post请求默认的都开启了csrf的模式,所有post请求都必须带有token之类的验证信息才可以进入登陆页面,这边是禁用csrf模式
http.csrf().disable();
http
//requestMatchers放在最前面,对需要匹配的的规则进行自定义与过滤
.requestMatchers().antMatchers("/oauth/**")
//拦截上面匹配后的url,需要认证后访问
.and()
.authorizeRequests().antMatchers("/oauth/**").authenticated()
.and()
.formLogin()
//用户登录自定义url
.loginProcessingUrl("/rest/hello")
//用户登录成功跳转url
.successForwardUrl("/rest/hello")
//用户登录失败跳转url
.failureForwardUrl("/rest/hello")
//使用 spring security 默认登录页面
.permitAll();
//http.addFilterBefore(new MyAuthenticationFilter("/login"), UsernamePasswordAuthenticationFilter.class);
http.sessionManagement()
.invalidSessionUrl("/login")
.maximumSessions(1)
.expiredUrl("/login");
}
}
2.2.4、调试及结果
下面用postman工具及浏览器对oauth2实例的四种模式进行调试及结果进行展示。
密码模式
密码模式需要参数:
username,password,grant_type,client_id,client_secret
grant_type类型password
操作步骤如下
①请求access_token
http://localhost:8080/oauth/token?username=demoUser1&password=123456&grant_type=password&client_id=demoApp&client_secret=demoAppSecret
请求成功结果如下
{
“access_token”: “e47f060a-27ff-4324-94b3-9bf947cb0e10”,
“token_type”: “bearer”,
“refresh_token”: “af6eb304-eb1d-4905-a0c2-efddd836a9f5”,
“expires_in”: 846,
“scope”: “all”
}
②根据获得的access_token进行资源请求
http://localhost:8080/rest/hello?access_token=e47f060a-27ff-4324-94b3-9bf947cb0e10
请求成功结果如下
③密码模式适用于用户对应用程序高度信任的情况。比如是用户操作系统的一部分。
后续会持续更新。。。