单体应用转变为分布式应用在架构方式上存在较大区别,单体应用下的简单架构方式以下:java
分布式应用的安全认证相对更加复杂,既要考虑它的安全性,一致性,还要考虑它的性能问题,开发成本等问题。分布式应用下的简单架构方式以下:git
单点登陆SSOgithub
统一的认证服务器(CAS)算法
缺点:每个用户请求都要通过认证服务器,容易造成瓶颈spring
Session共享数据库
Session共享存储,共享用户信息(Spring Session)安全
缺点:不一样平台、不一样架构中实现难度较高性能优化
Token认证服务器
认证服务器颁发包含用户身份信息的Token,资源服务器验证合法性(OAuth)微信
缺点:资源服务器想注销用户Token较困难
Token+Gateway
全部的请求经过网关,可在网关中完成认证或注销Token
缺点:Gateway必须保证高可用
OAuth是一个开放的协议,提供了标准受权方式去访问受保护的资源。
常见的使用者如:Twitter、微信、QQ、GitHub
(A)用户访问客户端,后者将前者导向认证服务器。
(B)用户选择是否给予客户端受权。
(C)假设用户给予受权,认证服务器将用户导向客户端事先指定的”重定向URI”(redirection URI),同时附上一个受权码。
(D)客户端收到受权码,附上早先的”重定向URI”,向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。
(E)认证服务器核对了受权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。
受权码模式是功能最完整、流程最严密的受权模式。
简化模式跳过了”受权码”这个步骤,使用较少
(A)用户向客户端提供用户名和密码。
(B)客户端将用户名和密码发给认证服务器,向后者请求令牌。
(C)认证服务器确认无误后,向客户端提供访问令牌。
密码模式一般使用在全部的服务都是由同一家公司提供的状况下。
请求示例:
http://localhost:9061/oauth/token?username=user_1&password=123456&grant_type=password&scope=all&client_id=client_2&client_secret=123456
响应示例:
{ "access_token": "8c95f119-2e2a-45c9-a70a-660e55205d6f", "token_type": "bearer", "refresh_token": "8033f033-488b-42d2-8bed-4623ab5f66a4", "expires_in": 43199, "scope": "all" }
(A)客户端向认证服务器进行身份认证,并要求一个访问令牌。
(B)认证服务器确认无误后,向客户端提供访问令牌。
为了保证安全,该模式通常会配合数字证书,须要使用客户端证书进行认证。
请求示例:
http://localhost:9061/oauth/token?grant_type=client_credentials&scope=all&client_id=client_1&client_secret=123456
响应示例:
{ "access_token": "47bdce37-d570-439b-9d63-48ca736dbd8c", "token_type": "bearer", "expires_in": 43199, "scope": "all" }
JSON Web Token(JWT)是一种用于传递认证信息的基于JSON的开放标准。
JWT可单独用于请求认证,也能够与其它认证协议配合使用,如OAuth2.0。
A)客户端传入用户密码请求JWT
B)认证中心验证经过后建立JWT并返回
C)客户端使用JWT访问资源服务器
D)资源服务器验证JWT,经过后返回资源内容
JWT 由三段信息构成,每一段内容都是一个 JSON 对象,将每一段 JSON 对象采用 BASE64 编码后,中间用 .
链接:
客户端模式请求示例:
http://localhost:9062/oauth/token?grant_type=client_credentials&scope=all&client_id=client_1&client_secret=123456
生成的JWT:
{ "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiZXRlcm1jbG91ZHMiXSwic2NvcGUiOlsiYWxsIl0sImV4cCI6MTUyMDg4ODYzOSwianRpIjoiZGJiMzk4MTAtYjVlOC00MjAwLTlkNzItZDBlMmYxYmMxMTM1IiwiY2xpZW50X2lkIjoiY2xpZW50XzEifQ.BdUvz5gI5GLsh-HmERYuCPcb0Z_94OkJBW8meYHIRQ4", "token_type": "bearer", "expires_in": 43199, "scope": "all", "jti": "dbb39810-b5e8-4200-9d72-d0e2f1bc1135" }
密码模式请求示例:
http://localhost:9062/oauth/token?username=user_1&password=123456&grant_type=password&scope=all&client_id=client_2&client_secret=123456
生成JWT:
{ "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiZXRlcm1jbG91ZHMiXSwidXNlcl9uYW1lIjoidXNlcl8xIiwic2NvcGUiOlsiYWxsIl0sImV4cCI6MTUyMDg4ODUxNCwiYXV0aG9yaXRpZXMiOlsiVFdUOlBFS1k3NzcwMjMsRVhUUklQOjEzNTg5OTkwMCJdLCJqdGkiOiJjY2E0YWFlYy1hNmVlLTQ4ZDktODBkYS1jYzgxN2E2ODM4NDQiLCJjbGllbnRfaWQiOiJjbGllbnRfMiJ9.lvVfQ5H23gIU_i2cMItdpVfdVkGUL7zI1Tbs59wZa74", "token_type": "bearer", "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiZXRlcm1jbG91ZHMiXSwidXNlcl9uYW1lIjoidXNlcl8xIiwic2NvcGUiOlsiYWxsIl0sImF0aSI6ImNjYTRhYWVjLWE2ZWUtNDhkOS04MGRhLWNjODE3YTY4Mzg0NCIsImV4cCI6MTUyMTQ1MDExNCwiYXV0aG9yaXRpZXMiOlsiVFdUOlBFS1k3NzcwMjMsRVhUUklQOjEzNTg5OTkwMCJdLCJqdGkiOiJlMzcxMjdhZC0yMzRiLTRkMDctODBiNC1lYzgzMmY2MzQ5YzciLCJjbGllbnRfaWQiOiJjbGllbnRfMiJ9.BswJy_3hyv_XqtM10sex1XRteJcDoqAwvogL3agt1KY", "expires_in": 43199, "scope": "all", "jti": "cca4aaec-a6ee-48d9-80da-cc817a683844" }
定义了标准格式,传递与解析简单
JWT更多的是一种开放TOKEN传递协议,对于多种业务场景(受权模式)的实现支持只停留在理伦层面。
OAuth方案中,得到了认证服务器颁发的Token后,资源服务器如何验证与鉴权呢?有几种方案:
资源服务器请求认证服务器验证Token
缺点:对认证服务器依赖太高,容易出现认证服务器性能问题
共享Token存储信息,如Redis共享
缺点:没法解决外部应用验证问题,如使用github认证服务器
资源服务器自验证,如JWT协议
缺点:Token注销是个难点,能够与Gateway结合控制。
实际由spring-security-oauth2提供实现。实现较为复杂。
Shiro实现OAuth2.0相对简单,但与spring cloud集成不是很好。
认证服务器主要须要配置:
资源服务器主要须要配置:
@Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { //配置安全策略 } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { //配置客户端认证信息 } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { //配置token的数据源、自定义的tokenServices等信息 }
配置示例:
/** * AuthorizationServerConfig.java * 功能:受权服务器配置 * @author kavenran 2018年2月23日 */ @Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private AuthenticationManager authenticationManager; @Autowired private DataSource dataSource; //自定义用户服务覆盖默认User @Autowired private UserDetailsServiceImpl userDetailsService; //token存储数据库 // @Bean // public JdbcTokenStore jdbcTokenStore() { // return new JdbcTokenStore(dataSource); // } //JWT存储Token @Bean public TokenStore jwtTokenStore() { return new JwtTokenStore(jwtAccessTokenConverter()); } //配置OAuth2的客户端相关信息,client信息包括:clientId、secret、scope、authorizedGrantTypes @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.withClientDetails(new JdbcClientDetailsService(dataSource));//基于oauth_client_token表的操做 } //配置身份认证器,配置认证方式,TokenStore,TokenGranter,OAuth2RequestFactory @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.tokenStore(jwtTokenStore()) .accessTokenConverter(jwtAccessTokenConverter()) .tokenServices(defaultTokenServices()) .authenticationManager(authenticationManager); } //Token校验方式采用JWT协议 @Bean public JwtAccessTokenConverter jwtAccessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); //CustomTokenEnhancer() //自定义Token信息 converter.setSigningKey("123"); return converter; } //按默认规则生成Token @Primary @Bean public DefaultTokenServices defaultTokenServices() { DefaultTokenServices tokenServices = new DefaultTokenServices(); tokenServices.setTokenStore(jwtTokenStore()); tokenServices.setSupportRefreshToken(true); tokenServices.setClientDetailsService(new JdbcClientDetailsService(dataSource)); tokenServices.setAccessTokenValiditySeconds(60 * 60 * 12); // token有效期自定义设置,默认12小时 tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);//默认30天,这里修改成7天 tokenServices.setTokenEnhancer(jwtAccessTokenConverter());//使用JWT生成密钥 return tokenServices; } //对应于配置AuthorizationServer安全认证的相关信息,建立ClientCredentialsTokenEndpointFilter核心过滤器 @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security.tokenKeyAccess("permitAll()"); security.checkTokenAccess("isAuthenticated()");//allow check token security.allowFormAuthenticationForClients(); } }
主要配置如下两个:
@Override public void configure(ResourceServerSecurityConfigurer resources) { //配置资源信息 } @Override public void configure(HttpSecurity http) throws Exception { //配置安全策略信息 }
配置示例:
/** * ResourceServerConfig.java * 功能:资源服务器配置 * @author kavenran * 2018年2月23日 */ @Configuration @EnableResourceServer public class ResourceServerConfig extends ResourceServerConfigurerAdapter{ //自定义的JWT @Autowired JwtTokenStore jwtTokenStore;//自定义的tokenStore //自定义的converter @Autowired JwtAccessTokenConverter jwtAccessTokenConverter; //设置资源ID,通过受权访问此资源ID的Token才能访问此资源 @Override public void configure(ResourceServerSecurityConfigurer resources) { resources.resourceId("order-service").tokenStore(jwtTokenStore); } @Override public void configure(HttpSecurity http) throws Exception { http .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) .and() .requestMatchers().anyRequest() .and() .anonymous() .and() .authorizeRequests() .antMatchers("/order/**").authenticated();//配置order访问控制,必须认证事后才能够访问 } }
OAuth自带的认证endpoint:
/oauth/authorize:验证 /oauth/token:获取token /oauth/confirm_access:用户受权 /oauth/error:认证失败 /oauth/check_token:资源服务器用来校验token /oauth/token_key:若是jwt模式则能够用此来从认证服务器获取公钥 刷新token是经过`oauth/token?grant_type=refresh_token`实现的
注:关注做者微信公众号,了解更多分布式架构、微服务、netty、MySQL、spring、、性能优化、等知识点。
公众号:《 Java大蜗牛 》