在 Spring Boot API 上实现 JWT 身份验证
Posted
技术标签:
【中文标题】在 Spring Boot API 上实现 JWT 身份验证【英文标题】:Implementing JWT Authentication on Spring Boot APIs 【发布时间】:2018-10-27 09:54:54 【问题描述】:我有一个 SpringBoot 2.0.2.RELEASE web 应用程序,带有这个配置文件:
@Override
protected void configure(HttpSecurity http) throws Exception
final List<String> activeProfiles = Arrays.asList(env.getActiveProfiles());
if (activeProfiles.contains("dev"))
http.csrf().disable();
http.headers().frameOptions().disable();
http
.authorizeRequests()
.antMatchers(publicMatchers()).permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login").defaultSuccessUrl("/bonanza/list")
.failureUrl("/login?error").permitAll()
.and()
.logout().permitAll();
我只想为匹配 /rest/**
的 Rest Controller 添加一个基于自定义 JWT 的安全过滤器,所以我将配置修改为这个文件,但现在我无法登录应用程序,因为我有 HTTP 状态 401 - 未授权
@Override
protected void configure(HttpSecurity http) throws Exception
final List<String> activeProfiles = Arrays.asList(env.getActiveProfiles());
if (activeProfiles.contains("dev"))
http.csrf().disable();
http.headers().frameOptions().disable();
http
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
// don't create session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers(publicMatchers()).permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login").defaultSuccessUrl("/bonanza/list")
.failureUrl("/login?error").permitAll()
.and()
.logout().permitAll();
// Custom JWT based security filter
JwtAuthorizationTokenFilter authenticationTokenFilter = new JwtAuthorizationTokenFilter(userDetailsService(), jwtTokenUtil, tokenHeader);
http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
和过滤器(从 OncePerRequestFilter
扩展)
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException
logger.info("processing authentication for ''", request.getRequestURL());
if (request.getRequestURI().indexOf("/rest/")==-1)
chain.doFilter(request, response);
return;
final String requestHeader = request.getHeader(this.tokenHeader);
String username = null;
String authToken = null;
if (requestHeader != null && requestHeader.startsWith("Bearer "))
authToken = requestHeader.substring(7);
try
username = jwtTokenUtil.getUsernameFromToken(authToken);
catch (IllegalArgumentException e)
logger.info("an error occured during getting username from token", e);
catch (ExpiredJwtException e)
logger.info("the token is expired and not valid anymore", e);
else
logger.info("couldn't find bearer string, will ignore the header");
logger.info("checking authentication for user ''", username);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null)
logger.info("security context was null, so authorizating user");
// It is not compelling necessary to load the use details from the database. You could also store the information
// in the token and read it from it. It's up to you ;)
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
// For simple validation it is completely sufficient to just check the token integrity. You don't have to call
// the database compellingly. Again it's up to you ;)
if (jwtTokenUtil.validateToken(authToken, userDetails))
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
logger.info("authorizated user '', setting security context", username);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
....
@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException
return request.getRequestURI().indexOf("/rest/")==-1;
在我看到的记录器中
("couldn't find bearer string, will ignore the header"
因为我只想在 RestContollers 中应用 JWT 过滤器,而不是在所有这些中应用,例如 LoginController
通过这个配置类,我可以访问仅在应用程序中登录的 /rest/ URL。
@Profile("web")
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
private static final Logger LOG = LoggerFactory.getLogger(WebSecurityConfig.class);
@Autowired
private UserSecurityService userSecurityService;
@Value("$server.servlet.context-path")
private String serverContextPath;
/** The encryption SALT. */
private static final String SALT = "fd&lkj§sfs23#$1*(_)nof";
@Bean
public BCryptPasswordEncoder passwordEncoder()
return new BCryptPasswordEncoder(12, new SecureRandom(SALT.getBytes()));
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception
return super.authenticationManagerBean();
@Configuration
@Order(1)
public static class ApiSecurityConfiguration extends WebSecurityConfigurerAdapter
@Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Value("$jwt.header")
private String tokenHeader;
@Value("$jwt.route.authentication.path")
private String authenticationPath;
@Override
protected void configure(HttpSecurity http) throws Exception
http
// we don't need CSRF because our token is invulnerable
.csrf().disable().exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
// don't create session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers(“/rest/**”).permitAll().anyRequest().authenticated()
.antMatchers(“**/rest/**”).permitAll().anyRequest().authenticated();
// Custom JWT based security filter
JwtAuthorizationTokenFilter authenticationTokenFilter = new JwtAuthorizationTokenFilter(userDetailsService(), jwtTokenUtil, tokenHeader);
http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
// disable page caching
http.headers().frameOptions().sameOrigin() // required to set for H2 else H2 Console will be blank.
.cacheControl();
@Configuration
@Order(0)
public static class OtherSecurityConfiguration extends WebSecurityConfigurerAdapter
@Value("$server.servlet.context-path")
private String serverContextPath;
@Autowired
private Environment env;
@Override
protected void configure(HttpSecurity http) throws Exception
final List<String> activeProfiles = Arrays.asList(env.getActiveProfiles());
if (activeProfiles.contains("dev"))
http.csrf().disable();
http.headers().frameOptions().disable();
http.authorizeRequests()
.antMatchers(publicMatchers())
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin().loginPage("/login").defaultSuccessUrl("/bonanza/list")
.failureUrl("/login?error").permitAll()
.and()
.logout()
.permitAll();
private String[] publicMatchers()
/** Public URLs. */
final String[] PUBLIC_MATCHERS =
"/webjars/**",
serverContextPath + "/css/**",
serverContextPath + "/js/**",
serverContextPath + "/fonts/**",
serverContextPath + "/images/**",
serverContextPath ,
"/",
"/error/**/*",
"/console/**",
ForgotMyPasswordController.FORGOT_PASSWORD_URL_MAPPING,
ForgotMyPasswordController.CHANGE_PASSWORD_PATH,
SignupController.SIGNUP_URL_MAPPING
;
return PUBLIC_MATCHERS;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception
auth.userDetailsService(userSecurityService).passwordEncoder(passwordEncoder());
【问题讨论】:
为什么不用antMatcher(“rest/**”).authenticated() 我必须在所有情况下都经过身份验证,rest/ 使用 JWT,其他使用典型的 springsecurity 假设你的过滤器是一个OncePerRequestFilter,它有一个方法shouldNotFilter(),你能用它来忽略你想要的请求之外的所有东西吗? 【参考方案1】:简而言之,您在同一个应用程序中有两个子路径(即/rest/**
和其他子路径),并且您希望为每个子路径应用不同的登录方案。 Spring-security 允许您拥有multiple configurations,允许这种情况。
我会这样做:
@EnableWebSecurity
public class SecurityConfig
@Configuration
@Order(1)
public static class ApiSecurityConfiguration
extends WebSecurityConfigurerAdapter
private final JwtAuthorizationTokenFilter jwtFilter = new ...
private final AuthenticationEntryPoint unauthorizedHandler = new ...
@Override
protected void configure(HttpSecurity http) throws Exception
http
.antMatcher("/rest/**").authorizeRequests()
.and()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler)
.and()
.addFilter(jwtFilter);
@Configuration
public static class OtherSecurityConfiguration
extends WebSecurityConfigurerAdapter
@Override
protected void configure(HttpSecurity http) throws Exception
http
.authorizeRequests().anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").defaultSuccessUrl("/bonanza/list")
.failureUrl("/login?error").permitAll()
.and()
.logout().permitAll();
使用这样的配置,JwtAuthorizationTokenFilter
应该只为匹配的路径激活。因此,我认为您不需要检查JwtAuthorizationTokenFilter
中的路径。
【讨论】:
以上是关于在 Spring Boot API 上实现 JWT 身份验证的主要内容,如果未能解决你的问题,请参考以下文章
使用Spring Security进行auth api调用时出现Cros错误
如何在 Spring Boot 中实现 oAuth2 和 JWT 身份验证? [关闭]
将 Angular 与 JWT 的 Spring Boot 连接时出现 CORS 错误
使用 Spring Boot 和 JWT 保护 REST Api
我已经在 Spring Boot 代码中实现了 JWT 令牌安全性。如何在我的代码中的任何地方获取 jwt 令牌?需要保存审核