SpringBoot 整合 oauth2(三)实现 token 认证
nanshan 2024-10-30 02:57 10 浏览 0 评论
内容导读
这里重写了security认证UserDetailsService 的接口方法,添加了自定义数据库密码的查询和校验。没搞过dubbo的朋友就把它当作是调用service层就行。描述: 从request的请求头中拿到Authorization信息,根据clientId获取到secret和请求头中的secret信息做对比,如果正确,组建一个新的TokenRequest类,然后根据前者和clientDetails创建OAuth2Request对象,然后根据前者和authentication创建OAuth2Authentication对象。注意是Basic 开头然后是clientid:clientScret 格式进行base64加密后的字符串。一般来讲,认证服务器是第三方提供的服务,比如你想接入qq登陆接口,那么认证服务器就是腾讯提供,然后你在本地做资源服务,但是认证和资源服务不是非要物理上的分离,只需要做到逻辑上的分离就好。认证服务中,我们获取到ClientDetailsServiceConfigurer 并设置clientId和secret还有令牌有效期,还有支持的授权模式,还有用户权限范围。
关于session和token的使用,网上争议一直很大。
总的来说争议在这里:
- session是空间换时间,而token是时间换空间。session占用空间,但是可以管理过期时间,token管理部了过期时间,但是不占用空间.
- sessionId失效问题和token内包含。
- session基于cookie,app请求并没有cookie 。
- token更加安全(每次请求都需要带上)。
开始正文了...
本文大概流程:
oauth2流程简介
我在这里只介绍常用的密码授权。
Oauth2 密码授权流程
在oauth2协议里,每一个应用都有自己的一个clientId和clientSecret(需要去认证方申请),所以一旦想通过认证,必须要有认证方下发的clientId和secret。
原理这块确实很麻烦,希望不理解的多看看参考文档。
1. pom
<!--security--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> </dependency>
2. 项目架构介绍
我们需要这七个类来完成。
3. UserDetail实现认证第一步
MyUserDetailsService.java
/** * Created by Fant.J. */ @Component public class MyUserDetailsService implements UserDetailsService { @Reference(version = "2.0.0") private UserService userService; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { String passwd = ""; System.out.println("收到的账号"+username); if (CheckFormat.isEmail(username)){ passwd = userService.selectPasswdByEmail(username); }else if (CheckFormat.isPhone(username)){ passwd = userService.selectPasswdByPhone(username); }else { throw new RuntimeException("登录账号不存在"); } System.out.println("查到的密码"+passwd); return new User(username, passwd, AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER")); } }
这里重写了security认证UserDetailsService 的接口方法,添加了自定义数据库密码的查询和校验。
为什么我把它放在了controller包了呢,因为我用的dubbo,@Reference注解扫描包是controller。这注解在别的包下失效。没搞过dubbo的朋友就把它当作是调用service层就行。
4. 认证成功/失败处理器
改动一:去掉了返回view还是json的判断,统一返回json。
改动二:修改登陆成功处理器,添加oauth2 客户端的认证。
MyAuthenticationFailHandler.java
/** * 自定义登录失败处理器 * Created by Fant.J. */ @Component public class MyAuthenticationFailHandler extends SimpleUrlAuthenticationFailureHandler { private Logger logger = LoggerFactory.getLogger(getClass()); @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { logger.info("登录失败"); //设置状态码 response.setStatus(500); response.setContentType("application/json;charset=UTF-8"); //将 登录失败 信息打包成json格式返回 response.getWriter().write(JSON.toJSONString(ServerResponse.createByErrorMessage(exception.getMessage()))); } }
MyAuthenticationSuccessHandler.java
/** * Created by Fant.J. */ @Component public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { private Logger logger = LoggerFactory.getLogger(getClass()); @Autowired private ClientDetailsService clientDetailsService; @Autowired private AuthorizationServerTokenServices authorizationServerTokenServices; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { logger.info("登录成功"); String header = request.getHeader("Authorization"); if (header == null && !header.startsWith("Basic")) { throw new UnapprovedClientAuthenticationException("请求投中无client信息"); } String[] tokens = this.extractAndDecodeHeader(header, request); assert tokens.length == 2; //获取clientId 和 clientSecret String clientId = tokens[0]; String clientSecret = tokens[1]; //获取 ClientDetails ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId); if (clientDetails == null){ throw new UnapprovedClientAuthenticationException("clientId 不存在"+clientId); //判断 方言 是否一致 }else if (!StringUtils.equals(clientDetails.getClientSecret(),clientSecret)){ throw new UnapprovedClientAuthenticationException("clientSecret 不匹配"+clientId); } //密码授权 模式, 组建 authentication TokenRequest tokenRequest = new TokenRequest(MapUtils.EMPTY_MAP,clientId,clientDetails.getScope(),"password"); OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails); OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request,authentication); OAuth2AccessToken token = authorizationServerTokenServices.createAccessToken(oAuth2Authentication); //判断是json 格式返回 还是 view 格式返回 //将 authention 信息打包成json格式返回 response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(JSON.toJSONString(ServerResponse.createBySuccess(token))); } /** * 解码请求头 */ private String[] extractAndDecodeHeader(String header, HttpServletRequest request) throws IOException { byte[] base64Token = header.substring(6).getBytes("UTF-8"); byte[] decoded; try { decoded = Base64.decode(base64Token); } catch (IllegalArgumentException var7) { throw new BadCredentialsException("Failed to decode basic authentication token"); } String token = new String(decoded, "UTF-8"); int delim = token.indexOf(":"); if (delim == -1) { throw new BadCredentialsException("Invalid basic authentication token"); } else { return new String[]{token.substring(0, delim), token.substring(delim + 1)}; } } }
描述:
从request的请求头中拿到Authorization信息,根据clientId获取到secret和请求头中的secret信息做对比,如果正确,组建一个新的TokenRequest类,然后根据前者和clientDetails创建OAuth2Request对象,然后根据前者和authentication创建OAuth2Authentication对象。最后通过AuthorizationServerTokenServices和前者前者创建OAuth2AccessToken对象。然后将token返回。
提示:
密码授权,我们在请求token的时候,需要一个包含clientid和clientSecret的请求头还有三个参数。
- 请求头:Authorization -> Basic aW50ZXJuZXRfcGx1czppbnRlcm5ldF9wbHVz 。注意是Basic 开头然后是clientid:clientScret 格式进行base64加密后的字符串。
- 请求参数:username和password是必须要的参数,值对应的就是账号密码,还有给可有可无的就是scope,它来声明该用户有多大的权限,默认是all。grant_type也是默认的参数,默认是password,它表示你以哪种认证模式来认证。
这是请求头解密现场
5. 配置认证/资源服务器
MyResourceServerConfig.java
/** * 资源服务器 * Created by Fant.J. */ @Configuration @EnableResourceServer public class MyResourceServerConfig extends ResourceServerConfigurerAdapter { @Autowired private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler; @Autowired private MyAuthenticationFailHandler myAuthenticationFailHandler; @Override public void configure(HttpSecurity http) throws Exception { //表单登录 方式 http.formLogin() .loginPage("/authentication/require") //登录需要经过的url请求 .loginProcessingUrl("/authentication/form") .successHandler(myAuthenticationSuccessHandler) .failureHandler(myAuthenticationFailHandler); http .authorizeRequests() .antMatchers("/user/*") .authenticated() .antMatchers("/oauth/token").permitAll() .anyRequest() .permitAll() .and() //关闭跨站请求防护 .csrf().disable(); } }
我这里只需要认证/user/*开头的url。@EnableResourceServer这个注解就决定了这十个资源服务器。它决定了哪些资源需要什么样的权限。
MyAuthorizationServerConfig.java
/** * 认证服务器 * Created by Fant.J. */ @Configuration @EnableAuthorizationServer public class MyAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private AuthenticationManager authenticationManager; @Autowired private UserDetailsService userDetailsService; @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { super.configure(security); } /** * 客户端配置(给谁发令牌) * @param clients * @throws Exception */ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory().withClient("internet_plus") .secret("internet_plus") //有效时间 2小时 .accessTokenValiditySeconds(72000) //密码授权模式和刷新令牌 .authorizedGrantTypes({"refresh_token","password"}) .scopes( "all"); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints .authenticationManager(authenticationManager) .userDetailsService(userDetailsService); } } }
@EnableAuthorizationServer就代表了它是个认证服务端。
一般来讲,认证服务器是第三方提供的服务,比如你想接入qq登陆接口,那么认证服务器就是腾讯提供,然后你在本地做资源服务,但是认证和资源服务不是非要物理上的分离,只需要做到逻辑上的分离就好。
认证服务中,我们获取到ClientDetailsServiceConfigurer 并设置clientId和secret还有令牌有效期,还有支持的授权模式,还有用户权限范围。
执行
好了,启动项目。
1. 给/oauth/token 发送post请求获取token
请求头:Authorization:Basic +clientid:secret 的base64加密字符串 (认证服务器中设置的client信息)
请求参数:username password (用户登陆账号密码)
{ "data": { "refreshToken": { "expiration": 1528892642111, "value": "xxxxxx-xxxxxx-xxxxx-xxxxxxxx" }, "scope": [ "all" ], "tokenType": "bearer", "value": "xxxxxx-xxxxxx-xxxxx-xxxxxxxx" }, "status": 200, "success": true }
2. 给/oauth/token 发送post请求刷新token
请求头: 不需要
请求参数:
- grant_type:refresh_token
- refresh_token:获取token时返回的refreshToken的value
3. 访问受保护的资源,比如/user/2
类型:get请求
请求头:Authorization: bearer + tokenValue
介绍下我的所有文集:
流行框架
底层实现原理:
相关推荐
- Let’s Encrypt免费搭建HTTPS网站
-
HTTPS(全称:HyperTextTransferProtocoloverSecureSocketLayer),是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入...
- 使用Nginx配置TCP负载均衡(nginx tcp负载)
-
假设Kubernetes集群已经配置好,我们将基于CentOS为Nginx创建一个虚拟机。以下是实验种设置的详细信息:Nginx(CenOS8Minimal)-192.168.1.50Kube...
- Nginx负载均衡及支持HTTPS与申请免费SSL证书
-
背景有两台minio文件服务器已做好集群配置,一台是192.168.56.41:9000;另一台是192.168.56.42:9000。应用程序通过Nginx负载均衡调用这两台minio服务,减轻单点...
- HTTPS配置实战(https配置文件)
-
原因现在网站使用HTTPS是规范操作之一,前些日子买了腾讯云服务,同时申请了域名http://www.asap2me.top/,目前该域名只支持HTTP,想升级为HTTPS。关于HTTPS的链接过程大...
- 只有IP地址没有域名实现HTTPS访问方法
-
一般来说,要实现HTTPS,得有个注册好的域名才行。但有时候呢,咱只有服务器的IP地址,没注册域名,这种特殊情况下,也能照样实现HTTPS安全访问,按下面这些步骤来就行:第一步,先确认公网...
- 超详解:HTTPS及配置Django+HTTPS开发环境
-
众所周知HTTP协议是以TCP协议为基石诞生的一个用于传输Web内容的一个网络协议,在“网络分层模型”中属于“应用层协议”的一种。在这里我们并不研究该协议标准本身,而是从安全角度去探究使用该协议传输数...
- Godaddy购买SSL之后Nginx配置流程以及各种错误的解决
-
完整流程:参考地址:https://sg.godaddy.com/zh/help/nginx-generate-csrs-certificate-signing-requests-3601生成NGI...
- Nginx从安装到高可用,一篇搞定(nginx安装与配置详解)
-
一、Nginx安装1、去官网http://nginx.org/下载对应的nginx包,推荐使用稳定版本2、上传nginx到linux系统3、安装依赖环境(1)安装gcc环境yuminstallgc...
- 阿里云免费证书申请,配置安装,使用tomcat,支持http/https访问
-
参数说明商品类型默认已选择云盾证书服务(无需修改)。云盾证书服务类型SSL证书服务的类型。默认已选择云盾SSL证书(无需修改),表示付费版SSL证书。如果您需要免费领取或付费扩容DV单域名证书【免费试...
- 你试过两步实现Nginx的规范配置吗?极速生成Nginx配置小工具
-
NGINX是一款轻量级的Web服务器,最强大的功能之一是能够有效地提供HTML和媒体文件等静态内容。NGINX使用异步事件驱动模型,在负载下提供可预测的性能。是当下最受欢迎的高性能的Web...
- 从零开始搭建HTTPS服务(搭建https网站)
-
搭建HTTPS服务的最初目的是为了开发微信小程序,因为wx.request只允许发起HTTPS请求,并且还必须和指定的域名进行网络通信。要从零开始搭建一个HTTPS的服务需要下面4...
- 群晖NAS使用官网域名和自己的域名配置SSL实现HTTPS访问
-
安全第一步,群晖NAS使用官网域名和自己的域名配置SSL实现HTTPS访问【新手导向】NAS本质还是一个可以随时随地访问的个人数据存储中心,我们在外网访问的时候,特别是在公网IP下,其实会面临着很多安...
- 让网站快速升级HTTPS协议提高安全性
-
为什么用HTTPS网络安全越来越受到重视,很多互联网服务网站,都已经升级改造为https协议。https协议下数据包是ssl/tcl加密的,而http包是明文传输。如果请求一旦被拦截,数据就会泄露产生...
- 用Https方式访问Harbor-1.9版本(https访问流程)
-
我上周在头条号写过一篇原创文章《Docker-Harbor&Docker-kitematic史上最详细双系统配置手册》,这篇算是它的姊妹篇吧。这篇文章也将用到我在头条写的另一篇原创文章的...
- 如何启用 HTTPS 并配置免费的 SSL 证书
-
在Linux服务器上启用HTTPS并配置免费的SSL证书(以Let'sEncrypt为例)可以通过以下步骤完成:---###**一、准备工作**1.**确保域名已解析**...
你 发表评论:
欢迎- 一周热门
-
-
极空间如何无损移机,新Z4 Pro又有哪些升级?极空间Z4 Pro深度体验
-
如何在安装前及安装后修改黑群晖的Mac地址和Sn系列号
-
爱折腾的特斯拉车主必看!手把手教你TESLAMATE的备份和恢复
-
10个免费文件中转服务站,分享文件简单方便,你知道几个?
-
[常用工具] OpenCV_contrib库在windows下编译使用指南
-
日本海上自卫队的军衔制度(日本海上自卫队的军衔制度是什么)
-
Ubuntu系统Daphne + Nginx + supervisor部署Django项目
-
【系统配置】信创终端挂载NAS共享全攻略:一步到位!
-
WindowsServer2022|配置NTP服务器的命令
-
UOS服务器操作系统防火墙设置(uos20关闭防火墙)
-
- 最近发表
- 标签列表
-
- linux 查询端口号 (58)
- docker映射容器目录到宿主机 (66)
- 杀端口 (60)
- yum更换阿里源 (62)
- internet explorer 增强的安全配置已启用 (65)
- linux自动挂载 (56)
- 禁用selinux (55)
- sysv-rc-conf (69)
- ubuntu防火墙状态查看 (64)
- windows server 2022激活密钥 (56)
- 无法与服务器建立安全连接是什么意思 (74)
- 443/80端口被占用怎么解决 (56)
- ping无法访问目标主机怎么解决 (58)
- fdatasync (59)
- 405 not allowed (56)
- 免备案虚拟主机zxhost (55)
- linux根据pid查看进程 (60)
- dhcp工具 (62)
- mysql 1045 (57)
- 宝塔远程工具 (56)
- ssh服务器拒绝了密码 请再试一次 (56)
- ubuntu卸载docker (56)
- linux查看nginx状态 (63)
- tomcat 乱码 (76)
- 2008r2激活序列号 (65)