oAuth2:是一种认证与授权的标准。
springSecurity:是一种提供oAuth2的安全框架加。
为什么需要oAuth2?
我们假设你有一个“云笔记”产品,并提供了“云笔记服务”和“云相册服务”,此时用户需要在不同的设备(PC、Android、iPhone、TV、Watch)上去访问这些“资源”(笔记,图片)
那么用户如何才能访问属于自己的那部分资源呢?此时传统的做法就是提供自己的账号和密码给我们的“云笔记”,登录成功后就可以获取资源了。但这样的做法会有以下几个问题:
“云笔记服务”和“云相册服务”会分别部署,难道我们要分别登录吗?
如果有第三方应用程序想要接入我们的“云笔记”,难道需要用户提供账号和密码给第三方应用程序,让他记录后再访问我们的资源吗?
用户如何限制第三方应用程序在我们“云笔记”的授权范围和使用期限?难道把所有资料都永久暴露给它吗?
如果用户修改了密码收回了权限,那么所有第三方应用程序会全部失效。
只要有一个接入的第三方应用程序遭到破解,那么用户的密码就会泄露,后果不堪设想。 为了解决如上问题,oAuth 应用而生。
oAuth2的常见名词解释:
第三方程序(Third-party application):又称客户端(client),我们会在这些设备中安装我们自己研发的 APP。又比如我们的产品想要使用 QQ、微信等第三方登录。对我们的产品来说,QQ、微信登录是第三方登录系统。我们又需要第三方登录系统的资(头像、昵称等)。对于 QQ、微信等系统我们又是第三方应用程序。
HTTP 服务提供商(HTTP service): 我们的云笔记产品以及 QQ、微信等都可以称之为“服务提供商”。
资源所有者(Resource Owner): 又称之为用户(user)。
用户代理(User Agent): 比如浏览器,代替用户去访问这些资源。
认证服务器(Authorization server): 即服务提供商专门用来处理认证的服务器,简单点说就是登录功能(验证用户的账号密码是否正确以及分配相应的权限)
资源服务器(Resource server): 即服务提供商存放用户生成的资源的服务器。它与认证服务器,可以是同一台服务器,也可以是不同的服务器。简单点说就是资源的访问入口,比如上节中提到的“云笔记服务”和“云相册服务”都可以称之为资源服务器。
交互过程:
创建认证服务器:
认证服务器:主要用于验证客户端身份和用户的省份等信息,若正确则发放授权码,通过授权码去拉取token,认证服务器有token后,可以去资源服务器获取资源。
- 导入securityoAuth2的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<version>2.1.4.RELEASE</version>
</dependency>
- 配置AuthorizationServerConfiguration类,该类继承AuthorizationServerConfigurerAdapter类,作用是配置用户信息(基于JDBC连接或者基于内存存储),例基于JDBC连接代码如下
package com.gssl.oAuth2.server.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
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.boot.jdbc.DataSourceBuilder;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import javax.sql.DataSource;
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
@Bean
@Primary
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
// 配置数据源(注意,我使用的是 HikariCP 连接池),以上注解是指定数据源,否则会有冲突
return DataSourceBuilder.create().build();
}
/**
* 配置以JDBC的方式存储token数据
* @return
*/
@Bean
public TokenStore tokenStore(){
return new JdbcTokenStore(dataSource());
}
/**
* 配置一JDBC的方式存储clientDetailsService数据
* @return
*/
@Bean
public ClientDetailsService jdbcClientDetails (){
return new JdbcClientDetailsService(dataSource());
}
@Autowired
BCryptPasswordEncoder passwordEncoder;
/**
* 配置客服端保存的方式
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 配置客户端以JDBC的方式保存信息
clients.withClientDetails(jdbcClientDetails());
}
/**
* 配置令牌保存的方式
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore());
}
}
配置服务器完全相关配置(对用户信息的配置)
package com.gssl.oAuth2.server.config;
import com.gssl.oAuth2.server.config.service.UserDetailsServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Bean
@Override
public UserDetailsService userDetailsService() {
return new UserDetailsServiceImpl();
}
@Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService());
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/oauth/check_token");
}
}
- application.yml
spring:
application:
name: oAuth2-server
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://127.0.0.1:3306/oAuth2?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: root
hikari:
minimum-idle: 5
idle-timeout: 600000
maximum-pool-size: 10
auto-commit: true
pool-name: MyHikariCP
max-lifetime: 1800000
connection-timeout: 30000
connection-test-query: SELECT 1
server:
port: 8080
mybatis:
type-aliases-package: com.gssl.oAuth2.server.domain
mapper-locations: classpath:mapper/*.xml
- 查询封装用户信息
package com.gssl.oAuth2.server.config.service;
import com.gssl.oAuth2.server.domain.TbPermission;
import com.gssl.oAuth2.server.domain.TbUser;
import com.gssl.oAuth2.server.service.TbPermissionService;
import com.gssl.oAuth2.server.service.TbUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private TbUserService tbUserService;
@Autowired
private TbPermissionService tbPermissionService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
TbUser tbUser = (TbUser) tbUserService.getByusername(username);
//GrantedAuthority权限、角色,User()方法中需要传入权限角色的集合作为参数即 List<GrantedAuthority>
List<GrantedAuthority> grantedAuthorities= new ArrayList<>();
if(tbUser!=null){
List<TbPermission> tbPermissions = tbPermissionService.selectByUserId(tbUser.getId());
for (TbPermission o:tbPermissions
) {
grantedAuthorities.add(new SimpleGrantedAuthority(o.getEnname()));
}
}
return new User(tbUser.getUsername(),tbUser.getPassword(),grantedAuthorities);
}
}
创建资源服务器
该部分实际上是对要资源的权限控制,自己配置资源的访问权限,通过认证服务器的token来认证服务器比较该用户是否有访问的权限
- 配置所需保护的资源路径及权限,需要与认证服务器配置的授权部分对应,建立ResourceServer
Configuration类继承ResourceServerConfigurationAdapter类重写configure(HttpSecurity http) 方法。
package com.gssl.oAuth2.resource.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 以下为配置所需保护的资源路径及权限,需要与认证服务器配置的授权部分对应,应当从数据库中读出来
.antMatchers("/").hasAuthority("SystemContent")
.antMatchers("/view/**").hasAuthority("SystemContentView")
.antMatchers("/insert/**").hasAuthority("SystemContentInsert")
.antMatchers("/update/**").hasAuthority("SystemContentUpdate")
.antMatchers("/delete/**").hasAuthority("SystemContentDelete");
}
}
操作流程
- 初始化资源服务器数据库
- POM 所需依赖同认证服务器
- 配置资源服务器
- 配置资源(Controller)
- 初始化资源服务
验证方式
1.请求认证服务器获取授权码
http://localhost:8080/oauth/authorize?client_id=client&response_type=code
var foo = 'bar';
2.登录认证
3.认证完成后通过服务器发放的code
//该地址为在oauth_client_details表中填写的回调地址
http://www.funtl.com/?code=1JuO6V
var foo = 'bar';
4.通过code去拉取token,通过 CURL 或是 Postman 请求
4.通过access_token去资源服务器拉取资源.
该文章转至https://funtl.com/zh/spring-security-oauth2/%E5%88%9B%E5%BB%BA%E8%B5%84%E6%BA%90%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%A8%A1%E5%9D%97.html#%E8%AE%BF%E9%97%AE%E8%B5%84%E6%BA%90,仅供自己总结复习。