统一身份认证
平台采用Spring Security 结合Spring Gateway,结合平台的身份认证服务器,可实现对多端接入的身份证认证,如可支持手机号/邮件/工号/身份证号等的唯一身份标认证,其接入的原理如下所示:
- 前端应用要访问平台的应用,首先向网关请求获取授权访问的令牌。
- 网关向认证服务器发起获取令牌的请求
- 认证服务器向网关返回令牌。
- 网关把令牌返回给客户端。
- 客户端携带令牌访问请求应用
- 网关向认证服务器检验令牌
- 认证服务器返回令牌校验信息
- 网关判断根据令牌有效,则放行可以访问具体的业务应用。
身份认证数据来源
平台的统一身份认证统一来源可由多种数据源来提供,平台默认的数据来源是来自用户组织中心的数据库,在应用实施过程中,可来源其他数据源,如来自AD域,如下是平台通过接入AD域的一个认证过程实现参考:
2. JPAAS 登录实现
以下主要介绍一下登录流程的代码,及网关和认证中心之间的交互问题。
gateway 作用
- 可以配置URL是否可以匿名访问。
- 验证用户令牌
如果需要授权的地址,没有令牌就会认为不合法。
- 在登录后可以对URL是否需要进行授权校验。
对URL进行校验时可以配置的。
认证中心作用
认证客户端通用组件
jpaas-auth-client-spring-boot-starter ,这个模块在是 jpaas-commons 的子模块。
这个模块被 gateway 和认证中心使用。
2.1 网关解析
网关安全配置
redxun:
oauth2:
token:
store:
type: redis
security:
ignore:
# 忽略认证的地址
httpUrls: >
/api-uaa/oauth/**,
/api-uaa/validata/**
auth:
urlPermission:
#是否开启url级别权限
enable: false
#配置只进行登录认证,不进行url权限认证的api
ignoreUrls: >
/api-user/menus/current,
/api-user/users/current,
/api-log/requestStat
#白名单
includeClientIds:
- webApp
renew:
#是否开启token自动续签(目前只有redis实现)
enable: true
#白名单
includeClientIds:
- webApp
这个配置相关的读取代码在 认证客户端通用组件 项目中,具体代码见:
com.redxun.oauth2.common.properties.SecurityProperties
2.1.1 匿名访问配置
如果忽略代码不配置,那么在默认的情况下下面的URL也是不需要登录就可以访问的。
使忽略配置生效代码:
com.redxun.gateway.config.ResourceServerConfiguration
2.1.2 验证URL是否允许被访问
校验代码:
com.redxun.gateway.auth.PermissionAuthManager
这个的作用时,如果用户登录了,是否还需要对URL进行授权验证。
校验代码如下:
com.redxun.oauth2.common.service.impl.DefaultPermissionServiceImpl
//校验方法:
hasPermission(Authentication authentication, String requestMethod, String requestURI)
校验逻辑:
1.如果用户匿名访问直接返回没有权限。
2.如果没有开启URL验证,那么直接返回有权限。
3.判断时超管,那么直接访问。
验证登录应用 是否在配置的白名单或黑名单中。
判断当前的URL是否在配置的不需要验证的名单中,如果在则表示有权限。
获取当前人的角色,如果角色为空,则没有权限。
7.对接口权限验证。
获取当前登录用户的角色,并和 资源的权限进行验证。
2.2 token 存储
登录token存储配置。
redxun:
//配置tokenstore
oauth2:
token:
store:
type: redis
这里配置为redis。
相关配置代码
com.redxun.oauth2.common.config.TokenStoreConfig
@Configuration
@ConditionalOnProperty(prefix = "redxun.oauth2.token.store", name = "type", havingValue = "redis", matchIfMissing = true)
@Import(AuthRedisTokenStore.class)
public class RedisTokenConfig {
}
2.3 登录过程
登录的接口地址/oauth/user/token
登录代码
com.redxun.oauth.controller.OAuth2Controller
登录方法:
@PostMapping(SecurityConstants.PASSWORD_LOGIN_PRO_URL)
public void getUserTokenInfo(
@ApiParam(required = true, name = "username", value = "账号") String username,
@ApiParam(required = true, name = "password", value = "密码") String password,
HttpServletRequest request, HttpServletResponse response) throws IOException {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password);
writerToken(request, response, token, "用户名或密码错误");
}
实现过程:
- 验证是否是合法的客户端。
- 获取用户信息。
这里通过 UserDetailServiceImpl 获取用户信息。 - 密码验证
public class DefaultPasswordConfig {
/**
* 装配BCryptPasswordEncoder用户密码的匹配
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
在获取用户信息后,springsecurity 会调用密码验证进行验证。
- 验证通过后会将验证信息写入到 redis 中。
- 返回token到前端。
{ "success": true, "message": "", "show": true, "code": 200, "data": { "access_token": "9e1de1d9-6e19-4e7f-84e1-17234b22867f", "token_type": "bearer", "refresh_token": "97345508-0608-427f-b645-662c06bb5c68", "expires_in": 16319, "scope": "all" } }
2.4 登录序列图
在这里介绍登录的整个调用的过程涉及到的代码与前后端的类的调用过程,以使得开发人员更加了解整个平台的登录认证过程。
[说明]
- 在jpaas-vue中的login.vue页面发起登录,输入用户名与密码提交
- 提交的地址为/oauth/user/token,即跳到jpaas-auth的项目的OAuth2Controller的login方法进行响应
- 在login方法中,根据用户名与密码,调用writerToken方法进行用户与密码的认证,其调用SecurityUtil的方法进行令牌生成
- 在SecurityUtil的login方法中,生成令牌并放至Security的上文中。
- UserDetailServiceImpl拿到令牌,调用loadUserByusername方法获取用户信息,Spring Security则根据获取到的用户信息进行用户名,密码,是否过期等信息的核对,符合条件则通过验证,进行信息返回
2.5 在访问时获取上下文用户的实现
在用户访问微服务时,需要通过代码获取当前上下文的用户信息。
这个实现如下:
在客户端访问时需要在请求头中 带着 验证token。
在过滤器中,先获取这个token,然后用这个token 去 redis中获取这个用户信息,并放入到上下文的线程变量中。
过滤器代码如下:public class LoginUserFilter implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { try { HttpServletRequest request=(HttpServletRequest)servletRequest; JPaasUser jPaasUser= SysUserUtil.getLoginUser(); if(jPaasUser!=null){ ContextUtil.setCurrentUser(jPaasUser); } filterChain.doFilter(request, servletResponse); } finally { ContextUtil.clearUser(); } } }
另外 我们在使用feign访问微服务时,也需要在微服务中获取上下文信息,这也就要求feign访问后端服务时,带上这个token信息,这个代码如下:
public class TokenRelayRequestIntecepor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;
HttpServletRequest request = attributes.getRequest();
String auth=request.getHeader(SecurityConstants.Authorization);
// 2. 将token传递
if (StringUtils.isNotBlank(auth)) {
requestTemplate.header(SecurityConstants.Authorization, auth);
}
}
}
2.6 退出登录
退出的原理如下:
- 客户端发送token 到退出 的URL地址。
退出登录地址如下:
/oauth/remove/token
- 根据token 在redis 删除登录登录信息。
退出代码如下:com.redxun.oauth.handler.OauthLogoutHandler