我的应用程序已经有了 Spring Security Cookie 机制,现在只针对 API,我需要添加基于 JWT Token 的身份验证机制.我正在使用 Spring Security 的 MultiHttpSecurityConfiguration 和两个嵌套类.
I already have Spring Security Cookie mechanism in place for my application, now only for the API's, I need to add JWT Token-based authentication mechanism. I'm using Spring Security's MultiHttpSecurityConfiguration with two nested class.
会话和 JWT 令牌机制是否应该一起包含在一个应用程序中是完全不同的问题,我需要实现两件事.
Whether both session and JWT token mechanism should be included together in one application or not is a different question altogether, I need to achieve two things.
package com.leadwinner.sms.config;
import java.util.Collections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import com.leadwinner.sms.CustomAuthenticationSuccessHandler;
import com.leadwinner.sms.CustomLogoutSuccessHandler;
import com.leadwinner.sms.config.jwt.JwtAuthenticationProvider;
import com.leadwinner.sms.config.jwt.JwtAuthenticationTokenFilter;
import com.leadwinner.sms.config.jwt.JwtSuccessHandler;
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
@ComponentScan(basePackages = "com.leadwinner.sms")
public class MultiHttpSecurityConfig {
@Autowired
@Qualifier("userServiceImpl")
private UserDetailsService userServiceImpl;
@Autowired
private JwtAuthenticationProvider authenticationProvider;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userServiceImpl).passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager() {
return new ProviderManager(Collections.singletonList(authenticationProvider));
}
@Configuration
@Order(1)
public static class JwtSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationTokenFilter jwtauthFilter;
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.antMatcher("/web/umgmt/**").authorizeRequests()
.antMatchers("/web/umgmt/**").authenticated()
.and()
.addFilterBefore(jwtauthFilter, UsernamePasswordAuthenticationFilter.class);
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
@Configuration
@Order(2)
public static class SecurityConfig extends WebSecurityConfigurerAdapter {
private final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);
@Bean
public CustomAuthenticationEntryPoint getBasicAuthEntryPoint() {
return new CustomAuthenticationEntryPoint();
}
@Override
public void configure(HttpSecurity http) throws Exception {
logger.info("http configure");
http
.antMatcher("/**").authorizeRequests()
.antMatchers("/login/authenticate").permitAll()
.antMatchers("/resources/js/**").permitAll()
.antMatchers("/resources/css/**").permitAll()
.antMatchers("/resources/images/**").permitAll()
.antMatchers("/web/initial/setup/**").permitAll()
.antMatchers("/dsinput/**").permitAll().antMatchers("/dsoutput/**").permitAll()
.and()
.formLogin()
.loginPage("/login").usernameParameter("employeeId").passwordParameter("password")
.successForwardUrl("/dashboard")
.defaultSuccessUrl("/dashboard", true)
.successHandler(customAuthenticationSuccessHandler())
.failureForwardUrl("/logout")
.loginProcessingUrl("/j_spring_security_check")
.and().logout()
.logoutSuccessUrl("/logout").logoutUrl("/j_spring_security_logout")
.logoutSuccessHandler(customLogoutSuccessHandler())
.permitAll()
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.and().sessionManagement()
.sessionFixation().none()
.sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
.invalidSessionUrl("/logout")
.and().exceptionHandling().accessDeniedPage("/logout").and().csrf().disable();
http.authorizeRequests().anyRequest().authenticated();
}
@Bean
public AuthenticationSuccessHandler customAuthenticationSuccessHandler() {
return new CustomAuthenticationSuccessHandler();
}
@Bean
public LogoutSuccessHandler customLogoutSuccessHandler() {
return new CustomLogoutSuccessHandler();
}
}
}
JwtAuthenticationTokenFilter.java
JwtAuthenticationTokenFilter.java
package com.leadwinner.sms.config.jwt;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String header = request.getHeader("Authorization");
if (header != null && header.startsWith("Bearer ")) {
String authToken = header.substring(7);
System.out.println(authToken);
try {
String username = jwtTokenUtil.getUsernameFromToken(authToken);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
if (jwtTokenUtil.validateToken(authToken, username)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
username, null, null);
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
} catch (Exception e) {
System.out.println("Unable to get JWT Token, possibly expired");
}
}
chain.doFilter(request, response);
}
}
JwtTokenUtil.java
JwtTokenUtil.java
package com.leadwinner.sms.config.jwt;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import org.springframework.stereotype.Component;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
@Component
public class JwtTokenUtil implements Serializable {
private static final long serialVersionUID = 8544329907338151549L;
public static final long JWT_TOKEN_VALIDITY = 5 * 60 * 60;
private String secret = "my-secret";
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
public String generateToken(String username) {
Map<String, Object> claims = new HashMap<>();
return doGenerateToken(claims, username);
}
private String doGenerateToken(Map<String, Object> claims, String subject) {
return "Bearer "
+ Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 1000))
.signWith(SignatureAlgorithm.HS512, secret).compact();
}
public Boolean validateToken(String token, String usernameFromToken) {
final String username = getUsernameFromToken(token);
return (username.equals(usernameFromToken) && !isTokenExpired(token));
}
}
现在 JwtSecurityConfig 过滤器似乎没有应用于我提到的路径.任何帮助将不胜感激.
It seems that now the JwtSecurityConfig filter is not being applied for the path I have mentioned. Any help will be appreciated.
我已经阅读了这个问题.我也跟着.
I have already read this question. I followed the same.
Spring 安全与 Spring Boot: 将基本身份验证与 JWT 令牌身份验证混合使用
添加了 JwtAuthenticationTokenFilter、JwtTokenUtil
Added JwtAuthenticationTokenFilter, JwtTokenUtil
我得到了你的要求.
您可以通过两个身份验证过滤器来实现这一点.
You can achieve this by two authentication filters.
Filter - 1:用于 Rest API (JwtAuthTokenFilter),它应该是无状态的,并由每次在请求中发送的授权令牌标识.
Filter - 2:你需要另一个过滤器(UsernamePasswordAuthenticationFilter) 默认情况下,如果你通过 http.formLogin()
配置它,spring-security 会提供这个.这里每个请求都由关联的会话(JSESSIONID
cookie)标识.如果请求不包含有效会话,那么它将被重定向到身份验证入口点(例如:登录页面).
Filter - 1: for Rest API (JwtAuthTokenFilter) which should be stateless and identified by Authorization token sent in request each time.
Filter - 2: You need another filter (UsernamePasswordAuthenticationFilter) By default spring-security provides this if you configure it by http.formLogin()
. Here each request is identified by the session(JSESSIONID
cookie) associated. If request does not contain valid session then it will be redirected to authentication-entry-point (say: login-page).
api-url-pattern = "/api/**" [strictly for @order(1)]
webApp-url-pattern = "/**" [ wild card "/**" always used for higer order otherwise next order configuration becomes dead configuration]
使用 @EnableWebSecurity
创建两个内部静态类,它们应该扩展 WebSecurityConfigurerAdapter
并使用@Configuration 和@Order 进行注释.这里rest api配置的顺序应该是1,Web应用程序配置的顺序应该大于1
Create two inner static classes which should extend WebSecurityConfigurerAdapter
and annotated with @Configuration and @Order. Here order for rest api configuration should be 1 and for web application configuration order should be more than 1
请参阅 我在此链接中的回答了解更多详情,其中有解释深度与必要的代码.如果需要,请随时从 github 存储库获取可下载链接.
Refer my answer in this link for more details which has explaination in depth with necessary code. Feel free to ask for downloadable link from github repository if required.
限制
在这里,两个过滤器将并排工作(并行).我的意思是来自 Web 应用程序,即使用户通过会话进行身份验证,他也无法在没有 JWT 令牌的情况下访问 API.
Limitation
Here both filters will work side by side(Parellally). I mean from web application even though if a user is authenticated by session, he can not access API's without a JWT token.
编辑
对于 OP 的要求,他不想定义任何角色,但允许经过身份验证的用户访问 API.为他的要求修改了下面的配置.
EDIT
For OP's requirement where he doesn't want to define any role but API access is allowed for authenticated user. For his requirement modified below configuration.
http.csrf().disable()
.antMatcher("/web/umgmt/**").authorizeRequests()
.antMatcher("/web/umgmt/**").authenticated() // use this
这篇关于Spring Security MultiHttpSecurity 配置使我可以执行两种类型的身份验证.JWT 令牌和会话 Cookie的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持html5模板网!