JSON Web Token(JWT)是目前最流行的跨域身份验证解决方案,在微服务环境下,我们可以借助JWT实现服务器的身份认证
基本过程如下图:
JWT的原则是在服务器身份验证之后,将生成一个JSON对象并将其发送回用户,如下所示。
{
"UserName": "xxx",
"Role": "Admin,User",
"Expire": "2019-06-01 10:13:26"
}
之后,当用户与服务器通信时,客户在请求中发回JSON对象。服务器仅依赖于这个JSON对象来标识用户。为了防止用户篡改数据,服务器将在生成对象时添加签名(有关详细信息,请参阅下文)。
服务器不保存任何会话数据,即服务器变为无状态,使其更容易扩展。
项目分成几个模块
<module>spc-zuul-gateway</module>
<module>base-framwork-jwt</module>
<module>spc-auth-center</module>
关键代码如下:
//扩展 Spring security 的 AbstractAuthenticationProcessingFilter
public class JwtUsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private final JwtAuthenticationConfig config;
private final ObjectMapper mapper;
private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
public JwtUsernamePasswordAuthenticationFilter(JwtAuthenticationConfig config, AuthenticationManager authManager) {
super(new AntPathRequestMatcher(config.getUrl(), "POST"));
setAuthenticationManager(authManager);
this.config = config;
this.mapper = new ObjectMapper();
}
@Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse rsp)
throws AuthenticationException, IOException {
String username= req.getParameter("username");
String password= req.getParameter("password");
User u = new User();
u.setUsername(username);
u.setPassword(password);
return getAuthenticationManager().authenticate(new UsernamePasswordAuthenticationToken(
u.getUsername(), u.getPassword(), Collections.emptyList()
));
}
@Override
protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse rsp, FilterChain chain,
Authentication auth) throws IOException, ServletException {
Instant now = Instant.now();
String token = Jwts.builder()
.setSubject(auth.getName())
.claim("authorities", auth.getAuthorities().stream()
.map(GrantedAuthority::getAuthority).collect(Collectors.toList()))
.setIssuedAt(Date.from(now))
.setExpiration(Date.from(now.plusSeconds(config.getExpiration())))
.signWith(SignatureAlgorithm.HS256, config.getSecret().getBytes())
.compact();
rsp.addHeader(config.getHeader(), config.getPrefix() + " " + token);
SecurityContextHolder.getContext().setAuthentication(auth);
successHandler.onAuthenticationSuccess(req, rsp, auth);
}
在认证服务器的过滤链中加入自定义的JwtUsernamePasswordAuthenticationFilter
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
JwtAuthenticationConfig config;
@Bean
public JwtAuthenticationConfig jwtConfig() {
return new JwtAuthenticationConfig();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
auth.inMemoryAuthentication().withUser("admin").password(encoder.encode("admin")).roles("ADMIN", "USER").and()
.withUser("hsn").password(encoder.encode("hsn")).roles("USER");
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable().logout().disable()
.addFilterBefore(new JwtUsernamePasswordAuthenticationFilter(config, authenticationManager()),
UsernamePasswordAuthenticationFilter.class)
.authorizeRequests().antMatchers("/").permitAll()
.antMatchers("/user/**").hasRole("USER")
.and()
.formLogin().loginPage("/login").defaultSuccessUrl("/user")
.and()
.logout().logoutUrl("/logout")
.logoutSuccessUrl("/login");
}
}
当用户登录后,将在返回头中保持认证信息,每次发起访问的时候,Zuul只需要对token进行验证,这样在调用后端服务之前进行权限的校验
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationConfig config;
@Bean
public JwtAuthenticationConfig jwtConfig() {
return new JwtAuthenticationConfig();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.logout().disable()
//.formLogin().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.anonymous()
.and()
.exceptionHandling().authenticationEntryPoint(
(req, rsp, e) -> rsp.sendError(HttpServletResponse.SC_UNAUTHORIZED))
.and()
.addFilterAfter(new JwtTokenAuthenticationFilter(config),
UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers(config.getUrl()).permitAll()
//.antMatchers("/swagger-ui.html").permitAll()
.antMatchers("/static/**").permitAll()
.antMatchers("/oauth/rest_token*").permitAll()
.antMatchers("/login*").permitAll()
.antMatchers("/user/**").hasAnyRole("ADMIN")
.antMatchers("/order/order/placeOrder/**").hasRole("ADMIN")
.antMatchers("/order/user").hasRole("USER")
.antMatchers("/order/guest").permitAll()
.antMatchers("/login*").anonymous();
//httpSecurity.authenticationProvider();
}
}
测试方法:
启动服务后,点击登录输入 admin/admin 登录成功,在返回头可以看到验证信息,下次调用带上这个信息即可