1、前言(单点登录的原理)
我们在做微服务架构中,一般会研发一个认证中心的应用(即一个微服务),其实这就是单点登录(Single Sign on,即SSO)。这个认证中心实现在多系统应用构成的集群中,登录其中任意一个系统,其它系统自动得到授权,从而无需再次登录,从而实现了单点的登录与单点注销两部分。
认证中心是一个独立的应用,即微服务,只有它能接受用户名和密码等安全信息,其他系统不提供登录入口,只接受认证中心的间接授权。间接授权通过令牌实现,so认证中心验证用户的用户名密码没问题,创建授权令牌,在接下来的跳转过程中,授权令牌作为参数发送给各个子系统,子系统拿到令牌,即得到了授权,可以借此创建局部会话,局部会话登录方式与单系统的登录方式相同。这个过程,也就是单点登录的原理。
2、OAuth2的实现,即做一个认证中心,或叫授权服务器的微服务
oauth2通常分为二块:认证服务和资源服务,这二个可以放在同一个应用中,也可以分为作为应用。即可以把认证中心做成一个微服务。
2.1 授权服务的实现
2.1.1.主要复写三个方法:
ClientDetailsServiceConfigurer:这个configurer定义了客户端细节服务。客户详细信息可以被初始化,为了灵活通用客户端的配置信息放在数据库表oauth_client_details(OAuth2自带的),通过jdbc引入,主要字段有:resource_ids(资源id标识),client_id,(相当于AppId)client_secret(即密钥,相当于AppSecret),scope(read/write/trust,多个权限逗号分开),authorized_grant_types(四种认证方式用哪一些),authorities(访问资源所需要的权限)等。
AuthorizationServerSecurityConfigurer:在令牌端点上定义了安全约束,这个一般配合WebSecurityConfig一起使用。
AuthorizationServerEndpointsConfigurer:定义了授权和令牌端点和令牌服务。
/*** 声明 ClientDetails实现*/@Beanpublic RedisClientDetailsService redisClientDetailsService(DataSource dataSource , RedisTemplate<String, Object> redisTemplate ) {RedisClientDetailsService clientDetailsService = new RedisClientDetailsService(dataSource);clientDetailsService.setRedisTemplate(redisTemplate);return clientDetailsService;}@Beanpublic RandomValueAuthorizationCodeServices authorizationCodeServices(RedisTemplate<String, Object> redisTemplate) {RedisAuthorizationCodeServices redisAuthorizationCodeServices = new RedisAuthorizationCodeServices();redisAuthorizationCodeServices.setRedisTemplate(redisTemplate);return redisAuthorizationCodeServices;}
/*** 配置身份认证器,配置认证方式,TokenStore,TokenGranter,OAuth2RequestFactory*/public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {//通用处理endpoints.tokenStore(tokenStore).authenticationManager(authenticationManager)// 支持.userDetailsService(userDetailsService);if(tokenStore instanceof JwtTokenStore){endpoints.accessTokenConverter(jwtAccessTokenConverter);}//处理授权码endpoints.authorizationCodeServices(authorizationCodeServices);// 处理 ExceptionTranslationFilter 抛出的异常endpoints.exceptionTranslator(webResponseExceptionTranslator);}/*** 配置应用名称 应用id* 配置OAuth2的客户端相关信息*/@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.withClientDetails(redisClientDetailsService);redisClientDetailsService.loadAllClientToCache();}/*** 对应于配置AuthorizationServer安全认证的相关信息,创建ClientCredentialsTokenEndpointFilter核心过滤器*/@Overridepublic void configure(AuthorizationServerSecurityConfigurer security) throws Exception {// url:/oauth/token_key,exposessecurity.tokenKeyAccess("permitAll()")/// public key for token/// verification if using/// JWT tokens// url:/oauth/check_token.checkTokenAccess("isAuthenticated()")// allow check token.allowFormAuthenticationForClients();}
2.1.2.开启spring security,即自定义WebSecurityConfigurerAdapter。
2.1.3. 自定义用户认证的实现:
认证是由AuthenticationManager 来管理的,即自定义AuthenticationProvider。注意AuthenticationManager 中可以定义有多个 AuthenticationProvider。
2.1.4 自定义UserDetailsService
自定义需要实现UserDetailsService接口,并且重写loadUserByUsername方法。返回的用户信息需要实现UserDatails接口。
2.2资源服务的实现
ResourceServerSecurityConfigurer主要配置内容:
tokenServices:ResourceServerTokenServices 类的实例,用来实现令牌业务逻辑服务resourceId:这个资源服务的ID,这个属性是可选的,但是推荐设置并在授权服务中进行验证tokenExtractor 令牌提取器用来提取请求中的令牌请求匹配器,用来设置需要进行保护的资源路径,默认的情况下是受保护资源服务的全部路径受保护资源的访问规则,默认的规则是简单的身份验证(plain authenticated)其他的自定义权限保护规则通过 HttpSecurity 来进行配置
例如代码:
@Configuration@EnableResourceServer@EnableConfigurationProperties(PermitUrlProperties.class)public class ResourceServerConfig extends ResourceServerConfigurerAdapter {@Autowiredprivate PermitUrlProperties permitUrlProperties;@Autowired(required = false)private TokenStore tokenStore;@Autowired private ObjectMapper objectMapper ; //springmvc启动时自动装配json处理类@Autowiredprivate OAuth2WebSecurityExpressionHandler expressionHandler;public void configure(WebSecurity web) throws Exception {web.ignoring().antMatchers("/health");web.ignoring().antMatchers("/oauth/user/token");web.ignoring().antMatchers("/oauth/client/token");}@Overridepublic void configure(ResourceServerSecurityConfigurer resources) throws Exception {if (tokenStore != null) {resources.tokenStore(tokenStore);} resources.stateless(true);resources.expressionHandler(expressionHandler);// 自定义异常处理端口 resources.authenticationEntryPoint(new AuthenticationEntryPoint() {@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response,AuthenticationException authException) throws IOException, ServletException {Map<String ,String > rsp =new HashMap<>(); response.setStatus(HttpStatus.UNAUTHORIZED.value() );rsp.put("resp_code", HttpStatus.UNAUTHORIZED.value() + "") ;rsp.put("resp_msg", authException.getMessage()) ;response.setContentType("application/json;charset=UTF-8");response.getWriter().write(objectMapper.writeValueAsString(rsp));response.getWriter().flush();response.getWriter().close();}});resources.accessDeniedHandler(new OAuth2AccessDeniedHandler(){@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException authException) throws IOException, ServletException {Map<String ,String > rsp =new HashMap<>(); response.setContentType("application/json;charset=UTF-8");response.setStatus(HttpStatus.UNAUTHORIZED.value() );rsp.put("resp_code", HttpStatus.UNAUTHORIZED.value() + "") ;rsp.put("resp_msg", authException.getMessage()) ;response.setContentType("application/json;charset=UTF-8");response.getWriter().write(objectMapper.writeValueAsString(rsp));response.getWriter().flush();response.getWriter().close();}});}@Overridepublic void configure(HttpSecurity http) throws Exception {http.requestMatcher(/*** 判断来源请求是否包含oauth2授权信息*/new RequestMatcher() {private AntPathMatcher antPathMatcher = new AntPathMatcher();@Overridepublic boolean matches(HttpServletRequest request) {// 请求参数中包含access_token参数if (request.getParameter(OAuth2AccessToken.ACCESS_TOKEN) != null) {return true;}// 头部的Authorization值以Bearer开头String auth = request.getHeader(UaaConstant.AUTHORIZTION);if (auth != null) {if (auth.startsWith(OAuth2AccessToken.BEARER_TYPE)) {return true;}}// 认证中心url特殊处理,返回true的,不会跳转login.html页面if (antPathMatcher.match(request.getRequestURI(), "/api-auth/oauth/userinfo")) {return true;}if (antPathMatcher.match(request.getRequestURI(), "/api-auth/oauth/remove/token")) {return true;}if (antPathMatcher.match(request.getRequestURI(), "/api-auth/oauth/get/token")) {return true;}if (antPathMatcher.match(request.getRequestURI(), "/api-auth/oauth/refresh/token")) {return true;}if (antPathMatcher.match(request.getRequestURI(), "/api-auth/oauth/token/list")) {return true;}if (antPathMatcher.match("/**/clients/**", request.getRequestURI())) {return true;}if (antPathMatcher.match("/**/services/**", request.getRequestURI())) {return true;}if (antPathMatcher.match("/**/redis/**", request.getRequestURI())) {return true;}return false;}}).authorizeRequests().antMatchers(permitUrlProperties.getIgnored()).permitAll().requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll().anyRequest().authenticated();}}
大话微服务:Spring Cloud gateway+OAuth2 实现单点登录和权限控制(五)OAuth2 认证服务中心的开发