赞
踩
一、oauth2认证中心:登录用户进行认证,生成token, 同时定义受保护的api服务
(一)oauth2的四种认证模式:授权码模式,简化模式,密码模式,客户端模式。其中授权码模式和密码模式用的最多。
A.OAuth2授权码模式: 我们进入一些第三方应用程序时,无需注册,只需要微信授权登录即可,对于我们的服务不需要存储用户的密码,只要存储认证平台返回的唯一ID和用户信息即可,这就是OAuth2常见的授权码模式,它的特点就是:利用第三方权威平台实现用户身份的认证,当然如果我们的公司里面很多微服务,我们自己也可以专门提取出一个认证中心,这个认证中心就是上面说的权威认证平台的角色,所有的微服务均要到这个认证中心做认证,从而实现了单点登录的功能。
B、OAuth2密码模式:通过用户账号和密码的输入,到认证中心获取到token,这个token就是标识用户的身份,一个网站平台或者一个APP,它可能有多个微服务组成,有了这个token后,在一段时间内可以任意访问这些微服务提供的相关api接口服务。
(二)常见的系统架构: 认证中心、微服务、客户端(app/web程序等终端)
客户端:通常就是指应用程序,可能是web或者手机app。
认证中心:OAuth2做账号认证生成token,这个token可以分两种:jwt token放在Header中,或者普通token放在redis中,当然OAuth2还可以当资源鉴权功能。
应用服务:即各个微服务,统一由认证中心做认证与api访问授权。
四、如何开发OAuth2认证中心(即认证服务端)功能:验证账号与密码、生成并存储token、检查token、刷新token等工作。
(1)pom.xml引入spring-cloud-starter-oauth2包,这个包中已经有了spring-cloud-starter-security,所以不需要再单独引入spring-cloud-starter-security。
(2)配置application.yml:主要redis和数据库的配置,OAuth2的一些表需要用到,例如客户端的配置表(oauth_client_details)。
(3)配置spring security:主要配置BCryptPasswordEncoder密码加密工具和忽略oauth2本身的api的拦截的内容。
很多人这个地方会疑惑为什么OAuth2还需要spring security(主要是这个WebSecurityConfigurerAdapter )?
答:这是因为OAuth2本身也同时是一个资源服务(它要对外暴露检查token的接口等),这就需要引入spring security
对授权服务本身的资源(即OAuth2本身的一些api) 进行保护,即OAuth2要开放哪些api。通过spring security(它的WebSecurityConfigurerAdapter )和oauth2的互相配合对不同的url进行访问的控制,通常在WebSecurityConfigurerAdapter 的类中WebSecurity可以配置一些忽略拦截的url的定义。
很多人这个地方还有一个疑惑有了spring security的WebSecurityConfigurerAdapter ,为什么还需要OAuth2的ResourceServerConfigurerAdapter?
答:WebSecurityConfigurerAdapter主要作用于用户的登录(form login,Basic auth),oauth的一些资源不需要拦截(这个不需要OAuth2保护的url),即设置忽略拦截的url定义,通常就是忽略OAuth2服务自身的一些api 接口的一些定义。
ResourceServerConfigurerAdapter: OAuth2保护一些微服务的资源,这些资源需要token验证后才能访问,主要是对client和token的认证。
ResourceServerConfigurerAdapter优先级高: 如果同时设置了对某一资源的访问控制,会以ResourceServerConfigurerAdapter设置的为准,因为ResourceServerConfigurerAdapter优先级更高,他会优先处理,而WebSecurityConfigurerAdapter会失效
(4) UserDetailsService的代码实现
核心的覆盖重写实现接口中loadUserByUsername方法,这个覆盖实现主要调用系统管理微服务中的用户api服务获取用户,或者验证用户的,一般与数据库的用户账号表密切关联。
(5) 编写AuthServerConfig配置类:
主要实现三个configure方法的重写,各个方法中主要对应以下即
AuthorizationServerEndpointsConfigurer参数的重写:让它支持password模式,设置用户验证服务,token的redis存储方式等。
ClientDetailsServiceConfigurer
参数的重写:定义各个客户端的约束条件。
public void configure(AuthorizationServerSecurityConfigurer方法的重写:限制客户端对认证接口的访问权限。
/** * 定义授权和令牌端点以及令牌服务 */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) { endpoints // 请求方式 .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST) // 指定token存储位置 .tokenStore(tokenStore()) // 自定义生成令牌 .tokenEnhancer(tokenEnhancer) // 用户账号密码认证 .userDetailsService(userDetailsService) // 指定认证管理器 .authenticationManager(authenticationManager) // 是否重复使用 refresh_token .reuseRefreshTokens(false) // 自定义异常处理 .exceptionTranslator(new CustomWebResponseExceptionTranslator()); }
- /**
- * 配置令牌端点(Token Endpoint)的安全约束
- */
- @Override
- public void configure(AuthorizationServerSecurityConfigurer oauthServer)
- {
- oauthServer.allowFormAuthenticationForClients().checkTokenAccess("permitAll()");
- }
- /**
- * 配置客户端详情
- */
- @Override
- public void configure(ClientDetailsServiceConfigurer clients) throws Exception
- {
- clients.withClientDetails(clientDetailsService());
- }
(6)token可以分来两种: jwt token和redisToken两种方案,根据需要选择,我推荐采用redisToken,jwtToken还需要设置一些配置类,同时配置到认证服务的端点上。
二、spring cloud gateway集成oauth2的支持,实现:网关服务、负责请求转发和鉴权功能
主要工作两块:
1.nacos中对于网关服务配置各个微服务的路由转向:
spring:
redis:
host: localhost
port: 6379
password: 123456
cloud:
gateway:
discovery:
locator:
lowerCaseServiceId: true
enabled: true
routes:
# 认证中心 ,id在全部路由定义中须要惟一,不能重复
- id: ebyte-auth
# lb代表从注册中心获取服务,且已负载均衡方式转发
uri: lb://ebyte-auth
# 转发规则定义/oauth/**请求都还转发至微服务ebyte-auth
predicates:
- Path=/oauth/**
filters:
# 自定义验证码处理过滤器
- ValidateCodeFilter
# StripPrefix去除掉上面path的第一个前缀,这个前缀更多是前端的api中定义的(前缀加了微服务的名称),和后端的api不符合
- StripPrefix=1
# 系统模块
- id: ebyte-system
uri: lb://ebyte-system
predicates:
- Path=/system/**
filters:
- StripPrefix=1
2.定义一些过滤器(结合业务实际,例如权限的控制,验证码等)。
3.其它一些相关知识:
在网关集成Oauth2.0后,我们的流程架构如上。主要逻辑如下:
1、客户端应用通过api网关请求认证服务器获取access_token http://localhost:8090/auth-service/oauth/token
2、认证服务器返回access_token
- {
- "access_token": "f938d0c1-9633-460d-acdd-f0693a6b5f4c",
- "token_type": "bearer",
- "refresh_token": "4baea735-3c0d-4dfd-b826-91c6772a0962",
- "expires_in": 43199,
- "scope": "web"
- }
3、客户端携带access_token通过API网关访问后端服务
4、API网关收到access_token后通过 AuthenticationWebFilter
对access_token认证
5、API网关转发后端请求,后端服务请求Oauth2认证服务器获取当前用户
另外:
典型的授权码模式:
三、spring cloud gateway集成kaptcha图形验证码(后来我改用了第三包easy-captcha的验证码生成包,更省事效果更佳)
步骤1:pom.xml中引入kaptcha包的引用.
步骤2:定义CaptchaConfig配置类,设置kaptcha图形验证码样式以及生成规则(可以配置自定义的文本的生成器KaptchaTextCreator类)
步骤3: 定义ValidateCodeHandler, 即验证码的生成
步骤4:最终的核心就是定义spring cloud gateway的路由配置类RouterFunctionConfiguration,主要把上面的验证码Hander(ValidateCodeHandler)加到路由上来,例如:
/** * 路由配置信息 * * @author zhongzk */ @Configuration public class RouterFunctionConfiguration { @Autowired private HystrixFallbackHandler hystrixFallbackHandler; @Autowired private ValidateCodeHandler imageCodeHandler; @SuppressWarnings("rawtypes") @Bean public RouterFunction routerFunction() { return RouterFunctions .route(RequestPredicates.path("/fallback").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), hystrixFallbackHandler) .andRoute(RequestPredicates.GET("/code").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), imageCodeHandler); } }
步骤5: 前端通过访问url http://ip:port/code 返回图形验证码,例如在vue中这样定义:
(1)api文件中定义:
// 获取验证码
- export function getCodeImg() {
- return request({
- url: '/code',
- method: 'get'
- })
- }
(2)登录页面定义:
- <div class="login-code">
- <img :src="codeUrl" @click="getCode" class="login-code-img"/>
- </div>
- methods: {
- getCode() {
- getCodeImg().then(res => {
- this.codeUrl = "data:image/gif;base64," + res.img;
- this.loginForm.uuid = res.uuid;
- });
- },
后端的生成验证码的api定义:
/** * 生成验证码 */ @Override public AjaxResult createCapcha() throws IOException, CaptchaException { // 生成验证码 String capText = producer.createText(); String capStr = capText.substring(0, capText.lastIndexOf("@")); String verifyCode = capText.substring(capText.lastIndexOf("@") + 1); BufferedImage image = producer.createImage(capStr); // 保存验证码信息 String uuid = IdUtils.simpleUUID(); String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid; System.out.println("验证码:" + verifyKey); redisService.setCacheObject(verifyKey, verifyCode, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES); // 转换流信息写出 FastByteArrayOutputStream os = new FastByteArrayOutputStream(); try { ImageIO.write(image, "jpg", os); } catch (IOException e) { return AjaxResult.error(e.getMessage()); } AjaxResult ajax = AjaxResult.success(); ajax.put("uuid", uuid); ajax.put("img", Base64.encode(os.toByteArray())); return ajax; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。