Zuul 集成Spring Security 与 JWT

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/h_sn9999/article/details/102692108

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 登录成功,在返回头可以看到验证信息,下次调用带上这个信息即可

完整代码请参考:https://github.com/hsn999/start-cloud

猜你喜欢

转载自blog.csdn.net/h_sn9999/article/details/102692108