1,Oauth2.0简介
Oauth2.0是一个关于授权(authorization)的开放网络标准,使得第三方应用有权力去访问Http服务。
Oauth2.0是一个开放标准,允许用户授权第三方应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方应用或分享他们数据的所有内容 --- 第三方认证。
2,Oauth2.0的四种授权模式
根据用户、第三方应用、授权服务器三者之间,彼此信任程度的不同,使用不同的授权模式。
1,code码模式authorization_code

- 用户同意了授权后,授权服务器会先给第三方应用颁发一个code码
- 第三方应用携带着code码再向授权服务器申请token
- 授权服务器向第三方应用颁发token
2,简化模式implicit
与code模式相比少了code码的申请,用户同意了授权后,授权服务器直接向第三方应用颁发token。
3,密码模式password

- 用户将自己的用户名和密码给了第三方应用
第三方应用通过用户的用户名和密码向授权服务器申请token。
4, 客户端模式client_credentials

第三方应用给授权服务器一个用户身份的标志直接向授权服务器申请token。
3,搭建授权服务器
1,引入依赖文件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--oauth2.0的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
版本控制
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.12.RELEASE</spring-boot.version>
<spring-cloud.version>Hoxton.SR12</spring-cloud.version>
</properties>
<!--SpringCloud版本的控制-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
2,编写配置文件application.yml
#服务名称
spring:
application:
name: auth-server
redis:
host: 127.0.0.1
port: 6379
database: 1
server:
port: 9999
3,修改主启动类
//开启授权服务器
@EnableAuthorizationServer
@SpringBootApplication
public class AuthServerApplication {
public static void main(String[] args) {
SpringApplication.run(AuthServerApplication.class, args);
}
//配置加密器到IOC容器---后面需要对密码进行加密处理
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
4,创建授权服务器的配置类
- 指定token的存储方式 – 先选择redis。
- 配置第三方应用 -- 只有配置的第三方应用才可向授权服务器申请token。
向资源服务器暴露token的存储方式。
@Configuration
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
//注入redis的连接工厂的bean对象
@Autowired
private RedisConnectionFactory redisConnectionFactory;
/*
配置TokenStore的Bean对象到IOC容器,指定token的存储方式:
选择Redis的TokenStore,表示使用Redis存放token;
*/
@Bean
public TokenStore tokenStore(){
return new RedisTokenStore(redisConnectionFactory);
}
//注入加密器
@Autowired
private PasswordEncoder passwordEncoder;
//配置第三方应用
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//在内存中配置第三方应用(第三方应用少的时候就放在内存中)
clients.inMemory()
//配置第三方应用的id和密码(唯一的标识每个第三方应用,自定义),假设百度是第三方
.withClient("baidu")
.secret(passwordEncoder.encode("baidu-secret"))//密码需要加密
//设置作用域(用作授权提示,没有实质作用,自定义)
.scopes("red")
//指定授权方式(code码方式authorization_code)
.authorizedGrantTypes("authorization_code")
//设置token过期时间(7200秒)
.accessTokenValiditySeconds(7200)
//访问成功后的重定向地址(必须是公网地址,且是https协议)
.redirectUris("https://www.baidu.com");
}
//向资源服务器暴露token的存储方式
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
//redis的TokenStore
endpoints.tokenStore(tokenStore());
}
}
5,创建SpringSecurity配置类,实现登录
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//注入加密器
@Autowired
private PasswordEncoder passwordEncoder;
//认证 -- 配置用户信息
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//在内存中配置一个用户
auth.inMemoryAuthentication()
.withUser("admin")//用户名
.password(passwordEncoder.encode("123"))//加密密码
.authorities("sys:query");//给用户随便分配个权限
}
//url访问授权
@Override
protected void configure(HttpSecurity http) throws Exception {
//提供一个登录表单
http.formLogin();
//所有请求登录后就可以请求
http.authorizeRequests().anyRequest().authenticated();
}
}
6,code模式
1,第三方应用申请code码
1.发送get请求。
2.http://localhost:9999/oauth/authorize固定url,ip、端口为授权服务器ip、端口。
3.请求参数response_type值为code,表示向授权服务器申请code码。
4.请求参数client_id值为第三方应用id,和配置类中配置的第三方应用的id一致。
5.请求参数state状态值,维护请求和回调之间的状态的不透明值,防止跨站点请求伪造,自定义。
6.请求参数redirect_uri访问成功后的重定向地址,和配置类中配置的第三方应用的地址一致。

2, 第三方应用携带code码申请token



7,简单授权模式
1,在授权服务器的配置类添加第三方应用
.and()
//再配置一个第三方应用,假设是新浪,id给xl,密码给xl-secret
.withClient("xl")
.secret(passwordEncoder.encode("xl-secret"))
.scopes("red")
//授权方式为implicit简单方式
.authorizedGrantTypes("implicit")
//token过期时间7200秒
.accessTokenValiditySeconds(7200)
//访问成功后的重定向地址
.redirectUris("https://www.sina.com.cn");
2,第三方应用申请token
8, 密码授权方式
1,修改SpringSecurity配置类
//向IOC容器中加入认证管理器--不需要表单让用户可隐式登录
@Bean
@Override
public AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
2,在授权服务器的配置类添加第三方应用
.and()
//再配置一个第三方应用,假设是网易,id给wy,密码给wy-secret
.withClient("wy")
.secret(passwordEncoder.encode("wy-secret"))
.scopes("red")
//授权方式为password密码方式
.authorizedGrantTypes("password")
//token过期时间7200秒
.accessTokenValiditySeconds(7200)
//访问成功后的重定向地址
.redirectUris("https://www.163.com");
3,修改配置类暴露认证管理器
//注入认证管理器
@Autowired
private AuthenticationManager authenticationManager;
//向资源服务器暴露token的存储方式
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
//redis的TokenStore
endpoints.tokenStore(tokenStore())
//暴露认证管理器
.authenticationManager(authenticationManager);
}
4,第三方应用申请token



9, 客户端授权方式
1,在授权服务器的配置类添加第三方应用
.and()
//再配置一个第三方应用,假设是搜狐,id给sh,密码给sh-secret
.withClient("sh")
.secret(passwordEncoder.encode("sh-secret"))
.scopes("read")
//授权方式为client_credentials客户端方式
.authorizedGrantTypes("client_credentials")
//token过期时间7200秒
.accessTokenValiditySeconds(7200)
//访问成功后的重定向地址
.redirectUris("https://www.sohu.com");
2,第三方应用申请token


4,授权服务器同时做资源服务器
1,配置授权服务器也为资源服务器
主运行类开启资源服务器@EnableResourceServer
2,编写controller类
@RestController
public class HelloController {
//处理/query的请求,然后向客户端响应字符串文本
@RequestMapping("/query")
public String query(){
return "query-ok";
}
//处理/add的请求,然后向客户端响应字符串文本
@RequestMapping("/add")
public String add(){
return "add-ok";
}
//处理/delete的请求,然后向客户端响应字符串文本
@RequestMapping("/delete")
public String delete(){
return "delete-ok";
}
//处理/update的请求,然后向客户端响应字符串文本
@RequestMapping("/update")
public String update(){
return "update-ok";
}
}
3,以密码授权方式进行测试拿取token




4,配置访问权限
1,SpringSecurity配置类开启方法级别的授权
@EnableGlobalMethodSecurity(prePostEnabled = true)
2,修改控制器
//具有sys:query权限的用户可访问该资源
@PreAuthorize("hasAnyAuthority('sys:query')")
//处理/query的请求,然后向客户端响应字符串文本
@RequestMapping("/query")
public String query(){
return "query-ok";
}
5,授权服务器和资源服务器分离
1,创建授权服务器的控制器
@RestController
public class UserController {
//处理/getUser的请求,响应封装了当前认证的用户信息的Principal对象
@RequestMapping("/getUser")
public Principal getUser(Principal principal){
return principal;
}
}
2,创建资源服务器boot项目并搭建环境,修改主程序类
//开启资源服务器
@EnableResourceServer
@SpringBootApplication
public class PayServiceApplication {
public static void main(String[] args) {
SpringApplication.run(PayServiceApplication.class, args);
}
}
3,修改配置文件
#资源服务器的服务名称
spring.application.name=pay-service
#资源服务器的端口
server.port=8080
#访问授权服务器的/getUser接口,拿到当前认证的用户的信息:
#1.可拿到授权服务器给当前认证的用户颁发的token,和用户请求资源服务器时携带的
# token对比是否相同,以决定用户是否可访问资源服务器;
#2.还可拿到当前认证的用户的权限等信息;
security.oauth2.resource.user-info-uri=http://localhost:9999/getUser
4,创建配置类,配置资源访问权限
//开启方法级别的授权
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
public class ResourceConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
// /free资源请求不需要认证直接放行
http.authorizeRequests().antMatchers("/free").permitAll();
//其它资源请求必须认证后才可访问
http.authorizeRequests().anyRequest().authenticated();
}
}
5,创建controller类
@RestController
public class PayController {
//处理/doPay的请求,向客户端响应字符串文本
@RequestMapping("/doPay")
public String doPay(){
return "支付完成";
}
//处理/queryPay的请求,向客户端响应字符串文本
@RequestMapping("/queryPay")
//具有sys:query权限的用户可访问该资源
@PreAuthorize("hasAnyAuthority('sys:query')")
public String queryPay(){
return "queryPay==ok";
}
//处理/addPay的请求,向客户端响应字符串文本
@RequestMapping("/addPay")
//具有sys:add权限的用户可访问该资源
@PreAuthorize("hasAnyAuthority('sys:add')")
public String addPay(){
return "addPay==ok";
}
//处理/deletePay的请求,向客户端响应字符串文本
@RequestMapping("/deletePay")
//具有sys:delete权限的用户可访问该资源
@PreAuthorize("hasAnyAuthority('sys:delete')")
public String deletePay(){
return "deletePay==ok";
}
//处理/updatePay的请求,向客户端响应字符串文本
@RequestMapping("/updatePay")
//具有sys:update权限的用户可访问该资源
@PreAuthorize("hasAnyAuthority('sys:update')")
public String updatePay(){
return "updatePay==ok";
}
}
6,测试访问
1,拿到token


2,携带token访问资源服务器

6,JWT token实现认证
1,原有模式
授权服务器压力大
第三方应用先通过授权服务器进行认证,拿到授权服务器颁发的token
携带token再去访问资源服务器中的资源
资源服务器都会去访问授权服务器,拿到授权服务器返回的token,和第三方应用携带的token进行校验比对

2,JWT token方式
资源服务器就不需要每次都去访问授权服务器,减轻授权服务器的压力。
第三方应用先通过授权服务器进行认证,认证通过授权服务器向第三方应用颁发jwt的token
第三方应用每次访问资源服务器的资源都携带jwt的token,资源服务器通过对第三方应用携带的jwt的token进行解析

3, 对称加密方式
1,改动授权服务器auth-server
@Configuration
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
//向IOC容器中配置jwt的token转换器
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
//创建jwt的token转换器
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
//设置签名 -- 头部和载体jwt的token转换器会自动处理,我们只关注签名
jwtAccessTokenConverter.setSigningKey("mmy-jwt");
return jwtAccessTokenConverter;
}
//注入jwt的token转换器
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
/*
配置TokenStore的Bean对象到IOC容器,指定token的存储方式:
创建一个jwt的TokenStore,表示使用jwt存放token;
*/
@Bean
public TokenStore tokenStore(){
return new JwtTokenStore(jwtAccessTokenConverter);
}
//注入加密器
@Autowired
private PasswordEncoder passwordEncoder;
//配置第三方应用
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//在内存中配置第三方应用(第三方应用少的时候就放在内存中)
clients.inMemory()
//配置第三方应用的id和密码(唯一的标识每个第三方应用,自定义),假设百度是第三方
.withClient("baidu")
.secret(passwordEncoder.encode("baidu-secret"))//密码需要加密
//设置作用域(用作授权提示,没有实质作用,自定义)
.scopes("red")
//指定授权方式(code码方式authorization_code)
.authorizedGrantTypes("authorization_code")
//设置token过期时间(7200秒)
.accessTokenValiditySeconds(7200)
//访问成功后的重定向地址(必须是公网地址,且是https协议)
.redirectUris("https://www.baidu.com")
.and().
//再配置一个第三方应用,假设是新浪,id给xl,密码给xl-secret
withClient("xl")
.secret(passwordEncoder.encode("xl-secret"))
.scopes("red")
//授权方式为implicit简单方式
.authorizedGrantTypes("implicit")
//token过期时间7200秒
.accessTokenValiditySeconds(7200)
//访问成功后的重定向地址
.redirectUris("https://www.sina.com.cn")
.and()
//再配置一个第三方应用,假设是网易,id给wy,密码给wy-secret
.withClient("wy")
.secret(passwordEncoder.encode("wy-secret"))
.scopes("red")
//授权方式为password密码方式
.authorizedGrantTypes("password")
//token过期时间7200秒
.accessTokenValiditySeconds(7200)
//访问成功后的重定向地址
.redirectUris("https://www.163.com")
.and()
//再配置一个第三方应用,假设是搜狐,id给sh,密码给sh-secret
.withClient("sh")
.secret(passwordEncoder.encode("sh-secret"))
.scopes("read")
//授权方式为client_credentials客户端方式
.authorizedGrantTypes("client_credentials")
//token过期时间7200秒
.accessTokenValiditySeconds(7200)
//访问成功后的重定向地址
.redirectUris("https://www.sohu.com");
}
//注入认证管理器
@Autowired
private AuthenticationManager authenticationManager;
//向资源服务器暴露token的存储方式
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
//jwt的TokenStore
endpoints.tokenStore(tokenStore())
//暴露认证管理器
.authenticationManager(authenticationManager)
//暴露jwt的token转换器
.accessTokenConverter(jwtAccessTokenConverter);
}
}
2,改动资源服务器pay-service
//开启方法级别的授权
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
public class ResourceConfig extends ResourceServerConfigurerAdapter {
//也向IOC容器中配置jwt的token转换器
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
//创建jwt的token转换器
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
//设置签名
jwtAccessTokenConverter.setSigningKey("mmy-jwt");
return jwtAccessTokenConverter;
}
//注入jwt的token转换器
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
/*
也配置TokenStore的Bean对象到IOC容器,指定token的存储方式:
创建一个jwt的TokenStore,表示使用jwt存放token;
*/
@Bean
public TokenStore tokenStore(){
return new JwtTokenStore(jwtAccessTokenConverter);
}
//重写configure(ResourceServerSecurityConfigurer)方法
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
//告诉资源服务器是jwt存放token,使用jwt的token转换器去解析token
resources.tokenStore(tokenStore());
}
@Override
public void configure(HttpSecurity http) throws Exception {
// /free请求不需要认证直接放行
http.authorizeRequests().antMatchers("/free").permitAll();
//其它资源请求必须认证后才可访问
http.authorizeRequests().anyRequest().authenticated();
}
}
3,修改配置文件
#不需要再访问授权服务器了
#security.oauth2.resource.user-info-uri=http://localhost:9999/getUser
4,测试



4, 非对称加密方式
对称加密;其存在一定的安全问题,签名用来生成token时做加密,如果签名泄露了,别人就也可以颁发token了。
1,安装OpenSSL
2,配置环境变量:
在PATH下加入此软件的路径
3,创建一个文件存在私钥
此文件目录下dos页面执行代码,mmy-jwt为别名,mmy-jwt.jks为存放私钥的文件名,mmy123为密码。
Keytool -genkeypair -alias mmy-jwt -validity 3650 -keyalg RSA –dname
"CN=jwt,OU=jtw,O=jwt,L=zurich,S=zurich,C=CH" -keypass mmy123 -keystore
mmy-jwt.jks -storepass mmy123
4, 生成公钥
此文件目录下dos页面执行代码mmy-jwt.jks为存放私钥的文件名
keytool -list -rfc --keystore mmy-jwt.jks | openssl x509 -inform pem -pubkey
5, 授权服务器auth-server使用私钥生成token
@Configuration
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
//向IOC容器中配置jwt的token转换器
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
//创建jwt的token转换器
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
//拿到存放私钥的文件
ClassPathResource classPathResource = new ClassPathResource("mmy-jwt.jks");
//创建钥匙工厂 --- mmy123为密码
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(classPathResource,
"mmy123".toCharArray());
//创建钥匙 --- mmy-jwt为别名
KeyPair keyPair = keyStoreKeyFactory.getKeyPair("mmy-jwt");
//给jwt的token转换器设置私钥
jwtAccessTokenConverter.setKeyPair(keyPair);
return jwtAccessTokenConverter;
}
//注入jwt的token转换器
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
/*
配置TokenStore的Bean对象到IOC容器,指定token的存储方式:
创建一个jwt的TokenStore,表示使用jwt存放token;
*/
@Bean
public TokenStore tokenStore(){
return new JwtTokenStore(jwtAccessTokenConverter);
}
//注入加密器
@Autowired
private PasswordEncoder passwordEncoder;
//配置第三方应用
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//在内存中配置第三方应用(第三方应用少的时候就放在内存中)
clients.inMemory()
//配置第三方应用的id和密码(唯一的标识每个第三方应用,自定义),假设百度是第三方
.withClient("baidu")
.secret(passwordEncoder.encode("baidu-secret"))//密码需要加密
//设置作用域(用作授权提示,没有实质作用,自定义)
.scopes("red")
//指定授权方式(code码方式authorization_code)
.authorizedGrantTypes("authorization_code")
//设置token过期时间(7200秒)
.accessTokenValiditySeconds(7200)
//访问成功后的重定向地址(必须是公网地址,且是https协议)
.redirectUris("https://www.baidu.com")
.and().
//再配置一个第三方应用,假设是新浪,id给xl,密码给xl-secret
withClient("xl")
.secret(passwordEncoder.encode("xl-secret"))
.scopes("red")
//授权方式为implicit简单方式
.authorizedGrantTypes("implicit")
//token过期时间7200秒
.accessTokenValiditySeconds(7200)
//访问成功后的重定向地址
.redirectUris("https://www.sina.com.cn")
.and()
//再配置一个第三方应用,假设是网易,id给wy,密码给wy-secret
.withClient("wy")
.secret(passwordEncoder.encode("wy-secret"))
.scopes("red")
//授权方式为password密码方式
.authorizedGrantTypes("password")
//token过期时间7200秒
.accessTokenValiditySeconds(7200)
//访问成功后的重定向地址
.redirectUris("https://www.163.com")
.and()
//再配置一个第三方应用,假设是搜狐,id给sh,密码给sh-secret
.withClient("sh")
.secret(passwordEncoder.encode("sh-secret"))
.scopes("read")
//授权方式为client_credentials客户端方式
.authorizedGrantTypes("client_credentials")
//token过期时间7200秒
.accessTokenValiditySeconds(7200)
//访问成功后的重定向地址
.redirectUris("https://www.sohu.com");
}
//注入认证管理器
@Autowired
private AuthenticationManager authenticationManager;
//向资源服务器暴露token的存储方式
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//jwt的TokenStore
endpoints.tokenStore(tokenStore())
//暴露认证管理器
.authenticationManager(authenticationManager)
//暴露jwt的token转换器
.accessTokenConverter(jwtAccessTokenConverter);
}
}
6,资源服务器pay-service使用公钥解析token
1,将公钥文件放入resource目录下

2,引入hutool的依赖:
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.11</version>
</dependency>
3,修改配置类
//开启方法级别的授权
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
public class ResourceConfig extends ResourceServerConfigurerAdapter {
//也向IOC容器中配置jwt的token转换器
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
//创建jwt的token转换器
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
//拿到存放公钥的文件
ClassPathResource classPathResource = new ClassPathResource("publicKey.txt");
//读取出公钥
String publicKey = null;
try {
publicKey = FileUtil.readString(classPathResource.getFile(),
Charset.defaultCharset());
} catch (IOException e) {
e.printStackTrace();
}
//给jwt的token转换器设置公钥
jwtAccessTokenConverter.setVerifierKey(publicKey);
return jwtAccessTokenConverter;
}
//注入jwt的token转换器
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
/*
也配置TokenStore的Bean对象到IOC容器,指定token的存储方式:
创建一个jwt的TokenStore,表示使用jwt存放token;
*/
@Bean
public TokenStore tokenStore(){
return new JwtTokenStore(jwtAccessTokenConverter);
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
//告诉资源服务器通过jwt解析token
resources.tokenStore(tokenStore());
}
@Override
public void configure(HttpSecurity http) throws Exception {
// /free请求不需要认证直接放行
http.authorizeRequests().antMatchers("/free").permitAll();
//其它资源请求必须认证后才可访问
http.authorizeRequests().anyRequest().authenticated();
}
}
7,测试




1796

被折叠的 条评论
为什么被折叠?



