传统的单体架构或者是多台服务器,一般都是采用session 来进行权限校验,这种方案是有弊端的:和用户信息强关联、用户多的情况下占用内存、有csrf网络攻击的风险等。而在微服务环境下通常采用 token 认证的方式校验是否有接口调用权限,然后在下游系统设置访问白名单只允许 zuul 服务器访问。理论上 zuul 服务器是不需要进行权限校验的,因为 zuul 服务器没有接口, 不需要从 zuul 调用业务接口,zuul 只做简单的路由工作。下游系统在获取到 token 后,通过 过滤器把 token 发到认证服务器校验该 token 是否有效,如果认证服务器校验通过就会携带 这个 token 相关的验证信息传回给下游系统,下游系统根据这个返回结果就知道该 token 具 有的权限是什么了。获取token 流程大概如下:
获取token的过程是怎样的呢:平台认证申请
Token 是通过一个单独的认证服务器来颁发的,只有具备了某种资质认证服务器才会把 token 给申请者。
平台认证申请往往是一个比较繁琐的过程,需要申请方提供比较完整的认证申请材料,比如 公司资质,营业执照等等信息,提交申请后认证方审核通过后,该平台才会允许申请 token。
具体获取token 的过程有4种模式方案:
一、客户端模式:
http://localhost:3030/oauth/token
我们可以看到客户端模式申请 token,只要带上有平台资质的客户端 id、客户端密码、然后
带上授权类型是客户端授权模式,带上 scope 就可以了。这里要注意的是客户端必须是具有
资质的。基于 redis 存储 token 信息的认证服务器搭建在 micro-security 工程 搭建:
1、pom文件引入jar:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
2、服务启动类核心配置代码:
@SpringBootApplication
@EnableEurekaClient
/*
* EnableResourceServer注解开启资源服务,因为程序需要对外暴露获取token的API和验证token的API所以该程序也是一个资源服务器
* */
@EnableResourceServer
public class MicroSecurityApplication {
public static void main(String[] args) {
SpringApplication.run(MicroSecurityApplication.class,args);
}
}
3、服务端认证代码:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
@Configuration
@EnableAuthorizationServer //认证服务器注解
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private RedisConnectionFactory connectionFactory;
@Autowired
private UserDetailsService userDetailsService;
//3、配置 token 存储方式
@Bean
public TokenStore tokenStore() {
return new MyRedisTokenStore(connectionFactory);
}
/*
* AuthorizationServerEndpointsConfigurer:用来配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)。
* */
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService)//若无,refresh_token会有UserDetailsService is required错误
.tokenStore(tokenStore()).
allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST);
}
/*
* AuthorizationServerSecurityConfigurer 用来配置令牌端点(Token Endpoint)的安全约束
* */
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
// 允许表单认证
security.allowFormAuthenticationForClients().tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
/*
ClientDetailsServiceConfigurer:用来配置客户端详情服务(ClientDetailsService),客户端详情信息在这里进行初始化,你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息
* 1.授权码模式(authorization code)
2.简化模式(implicit)
3.密码模式(resource owner password credentials)
4.客户端模式(client credentials)
* */
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
String finalSecret = "{bcrypt}" + new BCryptPasswordEncoder().encode("123456");
clients.
// jdbc(dataSource).
inMemory().
withClient("micro-web")
.resourceIds("micro-web")
.authorizedGrantTypes("client_credentials", "refresh_token")
.scopes("all","read", "write","aa")
.authorities("client_credentials")
.secret(finalSecret)
.accessTokenValiditySeconds(1200)
.refreshTokenValiditySeconds(50000)
.and()
.withClient("micro-zuul")
.resourceIds("micro-zuul")
.authorizedGrantTypes("password", "refresh_token")
.scopes("server")
.authorities("password")
.secret(finalSecret)
.accessTokenValiditySeconds(1200)
.refreshTokenValiditySeconds(50000);
}
}
5、Oauth2.0 权限校验认证服务器代码配置和客户端代码配置基本上是固定写法,这里知道如何
使用即可,关键是理解认证授权过程,oauth2.0 授权流程基本上是行业认证授权的标准了。
二、密码端模式:
1、密码模式获取 token,也就是说在获取 token 过程中必须带上用户的用户名和密码,获取到 的 token 是跟用户绑定的。
密码模式获取 token: 客户端 id 和客户端密码必须要经过 base64 算法加密,并且放到 header 中,
加密模式为 Base64(clientId:clientPassword),如下:
body中参数:
获取到的token:
2、密码模式认证服务器代码配置
3、客户端配置,客户端是可以存储在表中的
4、密码模式下游系统配置
properties 配置 指定客户端请求认证服务器接口
声明使用 oauth2.0 框架并说明这是一个客户端
开启权限的方法级别注解和指定拦截路径
认证服务器和下游系统权限校验流程图:
A、zuul 携带 token 请求下游系统,被下游系统 filter 拦截
B、下游系统过滤器根据配置中的 user-info-uri 请求到认证服务器
C、请求到认证服务器被 filter 拦截进行 token 校验,把 token 对应的用户、和权限从数据库
查询出来封装到 Principal
D、认证服务器 token 校验通过后过滤器放行执行 security/check 接口,把 principal 对象返回
E、下游系统接收到 principal 对象后就知道该 token 具备的权限了,就可以进行相应用户对
应的 token 的权限执行
三、授权码模式获取token:
授权码模式获取 token,在获取 token 之前需要有一个获取 code 的过程。
1、获取 code 的流程如下:
2、用户请求获取 code 的链接
http://localhost:7070/auth/oauth/authorize?client_id=pc&response_type=code&redirect_uri=htt p://localhost:8083/login/callback
3、提示要输入用户名密码
用户名秘密成功则会弹出界面
点击 approve 则会回调 redirect_uri 对应的回调地址并且把 code 附带到该回调地址里面
4、根据获取到的 code 获取 token
这里必须带上 redirect_uri 和 code,其他就跟前面的类似
1、客户端模式
一般用在无需用户登录的系统做接口的安全校验,因为 token 只需要跟客户端绑定,控制粒
度不够细
2、密码模式
密码模式,token 是跟用户绑定的,可以根据不同用户的角色和权限来控制不同用户的访问
权限,相对来说控制粒度更细
3、授权码模式
授权码模式更安全,因为前面的密码模式可能会存在密码泄露后,别人拿到密码也可以照样
的申请到 token 来进行接口访问,而授权码模式用户提供用户名和密码获取后,还需要有一
个回调过程,这个回调你可以想象成是用户的手机或者邮箱的回调,只有用户本人能收到这
个 code,即使用户名密码被盗也不会影响整个系统的安全。