SpringBoot 整合oauth2实现授权第三方应用(保证给你讲明白)
阅读本文大概需要 11 分钟。
来自:blog.csdn.net/u014365523/article/details/112317015
什么是OAuth2
1.快递员问题
2.授权机制的设计
第一步:门禁系统的密码输入器下方,增加一个按钮,叫做 “获取授权”。快递员需要进入小区的时候,首先按这个按钮,去申请授权。 第二步:他按下按钮以后,业主(也就是我)的手机就会跳出对话框:有人正在要求授权。系统会显示该快要的姓名,工号和所属的快递公司。我确认信息属实,就点击按钮,告诉门禁系统,我同意给予他进入这个小区的权限。 第三步:门禁系统得到我的确认以后,向快递员显示一个进入该下区的令牌(access token)。令牌就是类似密码的一串数字,只在短期能有效(可设置,比如7天) 第四步:快递员向门禁系统输入令牌,进入小区。
3.互联网应用场景
令牌是短期的,到期后自动失效,用户自己无法修改。密码一般长期有效,用户步修改,就不会发生变化 令牌可以被数据所有者撤销,会立即失效。如上例中,业主可以随时取消快递员的令牌。 令牌有权限反问(scope),比如只能进去小区的二号门,对于网络服务来说,只读令牌就比读写令牌更安全,密码一般是完整的权限。
注意:只要得到令牌,就能进入系统。系统一般不会再次确认身份,所以令牌必须保密,泄露令牌和泄漏密码的后果是一样的。这也是为什么令牌的有效期一般都设置的很短的原因。
4.OAuth2中的几个重要角色
资源所有者(Resource Owner):即代表授权客户端访问本身资源信息的用户,客户端访问用户帐户的权限仅限于用户授权的“范围”。 客户端(Client):即代表意图访问受限资源的第三方应用。在访问实现之前,它必须先经过用户者授权,并且获得的授权凭证将进一步由授权服务器进行验证。 授权服务器(Authorization Server):授权服务器用来验证用户提供的信息是否正确,并返回一个令牌给第三方应用。 资源服务器(Resource Server):资源服务器是提供给用户资源的服务器,例如头像、照片、视频等。
5.OAuth2 授权流程
步骤1:客户端(第三方应用)向用户请求授权。 步骤2:用户单击客户端所呈现的服务授权页面上的同意授权按钮后,服务端返回一个授权许可凭证给客户端。 步骤3:客户端拿着授权许可凭证去授权服务器申请令牌。 步骤4:授权服务器验证信息无误后,发放令牌给客户端。 步骤5:客户端拿着令牌去资源服务器访问资源。 步骤6:资源服务器验证令牌无误后开放资源。
注意:只要得到令牌,就能进入系统。系统一般不会再次确认身份,所以令牌必须保密,泄露令牌和泄漏密码的后果是一样的。这也是为什么令牌的有效期一般都设置的很短的原因。
6.OAuth2 授权模式
授权码模式:授权码模式(authorization code)是功能最完整、流程最严谨的授权模式。它的特点就是通过客户端的服务器与授权服务器进行交互,国内常见的第三方平台登录功能基本 都是使用这种模式。(最正统的方式,也是目前绝大多数系统所采用的)(支持refresh token) (用在服务端应用之间) 简化模式:简化模式不需要客户端服务器参与,直接在浏览器中向授权服务器中请令牌,一般若网站是纯静态页面,则可以采用这种方式。(为web浏览器应用设计)(不支持refresh token) (用在移动app或者web app,这些app是在用户的设备上的,如在手机上调起微信来进行认证授权) 密码模式:密码模式是用户把用户名密码直接告诉客户端,客户端使用这些信息向授权服务器中请令牌。这需要用户对客户端高度信任,例如客户端应用和服务提供商是同一家公司。(为遗留系统设计) (支持refresh token) 客户端模式:客户端模式是指客户端使用自己的名义而不是用户的名义向服务提供者申请授权。严格来说,客户端模式并不能算作 OAuth 协议要解决的问题的一种解决方案,但是,对于开发者而言,在一些前后端分离应用或者为移动端提供的认证授权服务器上使用这种模式还是非常方便的。(为后台api服务消费者设计) (不支持refresh token) (为后台api服务消费者设计)
实现步骤:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
<dependency>
<groupId>org.springframework.security.oauthgroupId>
<artifactId>spring-security-oauth2artifactId>
<version>2.3.3.RELEASEversion>
dependency>
AuthorizationServerConfigurerAdapter
,完成对授权服务器的配置,然后通过 @EnableAuthorizationServer
注解开启授权服务器:注意: authorizedGrantTypes(“password”, “refresh_token”)
表示 OAuth 2 中的授权模式为“password
”和“refresh_token
”两种。在标准的 OAuth 2 协议中,授权模式并不包括“refresh_token
”,但是在 Spring Security 的实现中将其归为一种,因此如果需要实现 access_token 的刷新,就需要这样一种授权模式。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
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.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
/**
* @Description: 配置授权服务
* @Author: Top
* @Version: V1.0
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
// 该对象用来支持 password 模式
@Autowired
AuthenticationManager authenticationManager;
// 该对象用来将令牌信息存储到内存中
@Autowired(required = false)
TokenStore inMemoryTokenStore;
// 该对象将为刷新token提供支持
@Autowired
UserDetailsService userDetailsService;
//指定密码的加密方式
@Bean
PasswordEncoder passwordEncoder() {
// 使用BCrypt强哈希函数加密方案(密钥迭代次数默认为10)
return new BCryptPasswordEncoder();
}
// 配置 password 授权模式
@Override
public void configure(ClientDetailsServiceConfigurer clients)
throws Exception {
// 这里client使用存在模式,可以实际过程调整为jdbc的方式
// 这里说明一下,redirectUris的连接可以是多个,这里通过access_token都可以访问的
// 简单点,就是授权的过程
clients.inMemory()
.withClient("password")
.authorizedGrantTypes("password", "refresh_token") //授权模式为password和refresh_token两种
.accessTokenValiditySeconds(1800) // 配置access_token的过期时间
.resourceIds("rid") //配置资源id
.scopes("all")
.secret("$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq"); //123加密后的密码
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.tokenStore(inMemoryTokenStore) //配置令牌的存储(这里存放在内存中)
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
// 表示支持 client_id 和 client_secret 做登录认证
security.allowFormAuthenticationForClients();
}
}
ResourceServerConfigurerAdapter
,并添加 @EnableResourceServer
注解开启资源服务器配置。import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
/**
* @Description: 配置资源服务
* @Author: Top
* @Version: V1.0
*/
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId("rid") // 配置资源id,这里的资源id和授权服务器中的资源id一致
.stateless(true); // 设置这些资源仅基于令牌认证
}
// 配置 URL 访问权限
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers("/admin/**").hasRole("admin")
.antMatchers("/user/**").hasRole("user")
.antMatchers("/secure/**").authenticated()
.anyRequest().permitAll();
}
}
注意:声明的顺序,必须先声明范围小的,再声明范围大的
oauth/**
”模式的 URL,这一类的请求直接放行。注意:在这个 Spring Security 配置和上面的资源服务器配置中,都涉及到了 HttpSecurity。其中 Spring Security 中的配置优先级高于资源服务器中的配置,即请求地址先经过 Spring Security 的 HttpSecurity,再经过资源服务器的 HttpSecurity。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
/**
* @Description: 配置 Security
* @Author: Top
* @Version: V1.0
*/
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
@Override
protected UserDetailsService userDetailsService() {
return super.userDetailsService();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin")
.password("$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq") //123
.roles("admin")
.and()
.withUser("sang")
.password("$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq") //123
.roles("user");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/oauth/**").authorizeRequests()
.antMatchers("/oauth/**").permitAll()
.and().csrf().disable();
}
}
5、Controller方法测试
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/secure")
public class UserController {
@PostMapping("/user")
public String user() { //需要提供访问的token才能访问,资源服务也需要验证token是否有效
System.out.println("张1");
return "非常自然1";
}
@GetMapping("/hello")
public String hello() { //需要提供访问的token才能访问,资源服务也需要验证token是否有效
System.out.println("张2");
return "非常自然2";
}
}
请求地址: oauth/token
请求参数:用户名、密码、授权模式、客户端 id、scope、以及客户端密码 返回结果: access_token
表示获取其它资源是要用的令牌,refresh_token
用来刷新令牌,expires_in
表示access_token
过期时间。
access_token
参数即可:access_token
过期后,可以使用 refresh_token
重新获取新的 access_token
(前提是 access_token
未过期),这里也是 POST 请求:请求地址: oauth/token
(不变)请求参数:授权模式(变成了 refresh_token
)、refresh_token
、客户端 id、以及客户端密码返回结果:与获取前面登录获取 token 返回的内容项一样。不过每次请求, access_token
和access_token
有效期都会变化。
user/hello
”接口,结果如下:https://www.hangge.com/blog/cache/detail_2683.html#
refresh token
http://localhost:8080/uaa/oauth/token?grant_type=password&username=lixx&password=dw123456
使用client_id和client_secret换取授权码,调用地址如: http://localhost:8097/oauth/authorize?response_type=code&client_id=password&redirect_uri=http://www.baidu.com
上述地址是被拦截的,跳转到登录页面: http://localhost:8097/login
,输入用户名、密码,登录之后返回第一地址,进行授权授权之后,跳转到回调地址,并携带授权码code,地址如: http://www.baidu.com?code=1LFD3E
根据授权码去获取token,地址如: http://localhost:8080/oauth/token?client_id=client_code&grant_type=authorization_code&redirect_uri=http://ww.baidu.com&client_secret=123456&code=nBYrX5
拿到token之后,访问需要授权的接口的时候,携带上就可以了
入骨相思知不知
玲珑骰子安红豆
入我相思门,知我相思苦,长相思兮长相忆,短相思兮无穷极。
朕已阅
评论