下面我们来讲下分布式 Security的授权 流程,只是蜻蜓点水,大佬勿喷!
一、需求分析
UUA认证服务负责认证授权- 所有请求经过网关到达微服务
- 网关负责鉴权客户端以及请求的转发
- 网关将
token解析后传给微服务,微服务进行授权
二、服务搭建
代码和环境搭建忽略,详情见:github.com/hucheng1997…
2.1 注册中心 Eureka 搭建
参考 https://hucheng.blog.csdn.net/article/details/105547229
2.2 网关 Zuul 搭建
网关整合 OAuth2有两种思路,一种是认证服务器生成 JWT令牌,所有请求统一在网关层验证,判断权限等操作;另一种是由资源服务器处理,网关只做请求的转发。
我们选用第一种,我们把 API网关作为 OAuth2的资源服务器角色,实现接入客户端权限拦截,令牌解析并转发当前登录用户信息(jsonToken)给微服务,这样下游微服务就需要关心令牌格式解析以及 OAuth2的相关机制。
API网关在认证授权体系里主要负责两件事:
- 作为
OAuth2.0的资源服务器角色,实现接入方权限拦截 - 令牌解析并转发当前登录用户信息(明文
token)给微服务
微服务拿到明文 token(包含登录用户的身份和权限信息)后也需要做两件事:
- 用户授权拦截(看当前用户是否有权访问该资源)
- 将用户信息存储进当前线程上下文(有利于后续业务逻辑随时获取当前用户信息)
在 Zuul中定义资源服务配置,主要配置的内容就是定义一些匹配规则,描述某个接入客户端需要什么样的权限才能访问某个微服务
@Configuration
public class ResourceServerConfig {
public static final String RESOURCE_ID = "res1";
@Configuration
@EnableResourceServer
public class UUAServerConfig extends ResourceServerConfigurerAdapter {
@Autowired
private TokenStore tokenStore;
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.tokenStore(tokenStore).resourceId(RESOURCE_ID)
.stateless(true);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/uua/**").permitAll();
}
}
@Configuration
@EnableResourceServer
public class OrderServerConfig extends ResourceServerConfigurerAdapter {
@Autowired
private TokenStore tokenStore;
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.tokenStore(tokenStore).resourceId(RESOURCE_ID)
.stateless(true);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/resource/**").access("#oauth2.hasScope('ALL')");
}
}
}
2.2.1 转发明文token给微服务
通过 Zuul过滤器的方式实现,目的是让下游微服务能够很方便的获取到当前的登录用户信息(明文 token)
①实现Zuul前置过滤器,完成当前登录用户信息提取,并放入转发微服务的request中
**
* token传递拦截
*/
public class AuthFilter extends ZuulFilter {
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (!(authentication instanceof OAuth2Authentication)) {
return null;
}
OAuth2Authentication oAuth2Authentication = (OAuth2Authentication) authentication;
Authentication userAuthentication = oAuth2Authentication.getUserAuthentication();
List<String> authorities = new ArrayList<>();
userAuthentication.getAuthorities().stream().forEach(s -> authorities.add(((GrantedAuthority) s).getAuthority()));
OAuth2Request oAuth2Request = oAuth2Authentication.getOAuth2Request();
Map<String, String> requestParameters = oAuth2Request.getRequestParameters();
Map<String,Object> jsonToken = new HashMap<>(requestParameters);
if (userAuthentication!=null){
jsonToken.put("principal",userAuthentication.getName());
jsonToken.put("authorities",authorities);
}
ctx.addZuulRequestHeader("json-token", EncryptUtil.encodeUTF8StringBase64(JSON.toJSONString(jsonToken)));
return null;
}
}
②将filter纳入spring 容器
@Configuration
public class ZuulConfig {
@Bean
public AuthFilter preFilter() {
return new AuthFilter();
}
@Bean
public FilterRegistrationBean corsFilter() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
config.setMaxAge(18000L);
source.registerCorsConfiguration("/**", config);
CorsFilter corsFilter = new CorsFilter(source);
FilterRegistrationBean bean = new FilterRegistrationBean(corsFilter);
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
}
2.2.2 微服务处理 token
微服务定义filter拦截token,并形成Spring Security的Authentication对象
@Component
public class TokenAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String token = request.getHeader("json-token");
if (token != null) {
String json = EncryptUtil.decodeUTF8StringBase64(token);
JSONObject jsonObject = JSON.parseObject(json);
UserDTO userDTO = JSON.parseObject(jsonObject.getString("principal"), UserDTO.class);
JSONArray authoritiesArray = jsonObject.getJSONArray("authorities");
String[] authorities = authoritiesArray.toArray(new String[authoritiesArray.size()]);
UsernamePasswordAuthenticationToken authenticationToken
= new UsernamePasswordAuthenticationToken(userDTO, null, AuthorityUtils.createAuthorityList(authorities));
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
filterChain.doFilter(request, response);
}
}
2.2.3 SpringSecurity 自定义 UserDetailsService
@Service
public class SpringDataUserDetailsService implements UserDetailsService {
@Autowired
UserDao userDao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserDto userDto = userDao.getUserByUsername(username);
if(userDto == null){
return null;
}
List<String> permissions = userDao.findPermissionsByUserId(userDto.getId());
String[] permissionArray = new String[permissions.size()];
permissions.toArray(permissionArray);
String principal = JSON.toJSONString(userDto);
UserDetails userDetails = User.withUsername(principal).password(userDto.getPassword()).authorities(permissionArray).build();
return userDetails;
}
}