Spring Cloud Security OAuth2结合网关
Spring Cloud Security OAuth2 整合网关,网关对token进行处理,转发明文给微服务,微服务解析明文
·
文章目录
一、需求分析
UUA
认证服务负责认证授权- 所有请求经过网关到达微服务
- 网关负责鉴权客户端以及请求的转发
- 网关将
token
解析后传给微服务,微服务进行授权
二、服务搭建
代码和环境搭建忽略,详情见:https://github.com/hucheng1997/security-oauth2
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";
/**
* uua令牌服务配置
*/
@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 authentication = SecurityContextHolder.getContext().getAuthentication();
if (!(authentication instanceof OAuth2Authentication)) {
return null;
}
OAuth2Authentication oAuth2Authentication = (OAuth2Authentication) authentication;
Authentication userAuthentication = oAuth2Authentication.getUserAuthentication();
/**
* 组装明文token,转发给微服务,放入head中,名称为json-token
*/
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);
}
//把身份信息和权限信息放在json中,加入http的header中,转发给微服务
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);
//将token转成json对象
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()]);
//将用户信息和权限填充 到用户身份token对象中
UsernamePasswordAuthenticationToken authenticationToken
= new UsernamePasswordAuthenticationToken(userDTO, null, AuthorityUtils.createAuthorityList(authorities));
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
//将authenticationToken填充到安全上下文
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){
//如果用户查不到,返回null,由provider来抛出异常
return null;
}
//根据用户的id查询用户的权限
List<String> permissions = userDao.findPermissionsByUserId(userDto.getId());
//将permissions转成数组
String[] permissionArray = new String[permissions.size()];
permissions.toArray(permissionArray);
//将userDto转成json
String principal = JSON.toJSONString(userDto);
UserDetails userDetails = User.withUsername(principal).password(userDto.getPassword()).authorities(permissionArray).build();
return userDetails;
}
}
更多推荐
已为社区贡献11条内容
所有评论(0)