认证
Oauth实现微信登录流程
安全认证:你是谁,能干什么
第三方登录授权过程
1. 用户访问第三方资源,第三方请求微信服务器发送授权请求给用户
2. 用户确认授权后,返回授权码给第三方
3. 第三方拿到授权码后,携带授权码向微信申请令牌
4. 第三方拿到令牌后,携带令牌向微信请求用户的基本信息
5. 微信资源服务器根据访问令牌,返回给第三方用户的基本信息
3-5的交互过程用户看不到,用户只能看到登录成功,显示自己的基本信息
账号密码注册登录
注册时候前端可以先加密,后端再加密一次,这样如果请求被拦截了也不会显示明文密码。
账号密码登录
1. 账号密码登录,网关对于登录和一些静态资源是放行的,可以访问。然后这里是验证登录和生成令牌的过程,这里采用jwt的方式经过RSA的私钥生成令牌,公钥去解析。
2. 在认证服务的过程中会去链接数据库,访问两个数据库,一个是通过fegin的远程调用访问user,role,perssion表(都是多对多),这样返回的时候就会携带用户和权限信息,一个是oauth_client_details(配置数据库链接,自动访问)拿到第三方授权的账号密码(自关联)。有用户名密码查询正确,把uid和生成令牌的jti形成键值对放入cookie中,redis存放的是jti和对应的token信息,这样就能通过cookie中的uid找到jti,再找到redis中的token可以进行解析了
3. 当用户访问服务的时候,会经过微服务网关,这里有两种配置解析的方式。
1.公钥存放在网关,判断cookie,redis中的指定值是否存在,最后通过公钥去解析令牌是否合法,如果都没问题就放行
2.如果私钥配置在微服务上的话,网关判断cookie和redis非空后,设置一下请求的响应头
request.mutate().header("Authorization","Bearer "+jwt);
这就是令牌的传递,这样其他微服务就不需要再去cookie和redis去找令牌了,过来的时候就携带了
现在就分为认证服务和资源服务
登录授权后如果有改变权限或者增加微服务地址怎么动态的增加呢?
可以在后台维护一张字典表,专门用作配置,比如要令牌资源路径,或者订单状态,维护一对多的两张表,保存在DB中,取完保存在redis中,用Quartz定时组件来完成定时的更新。
权限控制
1.类方式
···java
/*****
* 自定义授权认证类
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
ClientDetailsService clientDetailsService;
@Autowired
private UserFeign userFeign;
/****
* 自定义授权认证
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username)throws UsernameNotFoundException {
//取出身份,如果身份为空说明没有认证
Authentication authentication =SecurityContextHolder.getContext().getAuthentication();
//没有认证统一采用httpbasic认证,httpbasic中存储了client_id和client_secret,开始认证client_id和client_secret
if(authentication==null){
ClientDetails clientDetails =clientDetailsService.loadClientByClientId(username);
if(clientDetails!=null){
//秘钥
String clientSecret =clientDetails.getClientSecret();
//静态方式
//return new User(username,new BCryptPasswordEncoder().encode(clientSecret), AuthorityUtils.commaSeparatedStringToAuthorityList(""));
//数据库查找方式
return new User(username,clientSecret,AuthorityUtils.commaSeparatedStringToAuthorityList(""));
}
}
if (StringUtils.isEmpty(username)) {
return null;
}
//根据用户名查询用户信息
//String pwd = new BCryptPasswordEncoder().encode("itheima");
com.changgou.user.pojo.User user =userFeign.findUserInfo(username);
//创建User对象
String permissions ="salesman,accountant,user";
UserJwt userDetails =new UserJwt(username,user.getPassword(),AuthorityUtils.commaSeparatedStringToAuthorityList(permissions));
return userDetails;
}
}
···
2.注解方式
ResourceServerConfig类上添加@EnableGlobalMethodSecurity注解,用于开启@PreAuthorize的支持
···java
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled =true, securedEnabled =true)//激活方法上的PreAuthorize注解
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
//公钥
private static final String PUBLIC_KEY ="public.key";
/***
* 定义JwtTokenStore
* @param jwtAccessTokenConverter
* @return
*/
@Bean
public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
return new JwtTokenStore(jwtAccessTokenConverter);
}
/***
* 定义JJwtAccessTokenConverter
* @return
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter =new JwtAccessTokenConverter();
converter.setVerifierKey(getPubKey());
return converter;
}
/**
* 获取非对称加密公钥 Key
* @return 公钥 Key
*/
private String getPubKey() {
Resource resource =new ClassPathResource(PUBLIC_KEY);
try {
InputStreamReader inputStreamReader =new InputStreamReader(resource.getInputStream());
BufferedReader br =new BufferedReader(inputStreamReader);
return br.lines().collect(Collectors.joining("\n"));
}catch (IOException ioe) {
return null;
}
}
/***
* Http安全配置,对每个到达系统的http请求链接进行校验
* @param http
* @throws Exception
*/
@Override
public void configure(HttpSecurity http)throws Exception {
//所有请求必须认证通过
http.authorizeRequests()
//下边的路径放行
.antMatchers(
"/user/add","/user/load/**").//配置地址放行
permitAll()
.anyRequest().
authenticated();//其他地址需要认证授权
}
}
···
接下来可以在方法上加入@PreAuthorize(具体控制)
如果希望一个方法能被多个角色访问,配置:@PreAuthorize("hasAnyAuthority('admin','user')")
如果希望一个类都能被多个角色访问,在类上配置:@PreAuthorize("hasAnyAuthority('admin','user')")