問題描述
我的應(yīng)用程序已經(jīng)有了 Spring Security Cookie 機(jī)制,現(xiàn)在只針對(duì) API,我需要添加基于 JWT Token 的身份驗(yàn)證機(jī)制.我正在使用 Spring Security 的 MultiHttpSecurityConfiguration 和兩個(gè)嵌套類.
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.
會(huì)話和 JWT 令牌機(jī)制是否應(yīng)該一起包含在一個(gè)應(yīng)用程序中是完全不同的問題,我需要實(shí)現(xiàn)兩件事.
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.
- Spring Security 的基于會(huì)話的 cookie 身份驗(yàn)證將像以前一樣工作.
- 需要為 API 添加一個(gè)身份驗(yàn)證標(biāo)頭
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));
}
}
現(xiàn)在 JwtSecurityConfig 過濾器似乎沒有應(yīng)用于我提到的路徑.任何幫助將不勝感激.
It seems that now the JwtSecurityConfig filter is not being applied for the path I have mentioned. Any help will be appreciated.
我已經(jīng)閱讀了這個(gè)問題.我也跟著.
I have already read this question. I followed the same.
Spring 安全與 Spring Boot: 將基本身份驗(yàn)證與 JWT 令牌身份驗(yàn)證混合使用
添加了 JwtAuthenticationTokenFilter、JwtTokenUtil
Added JwtAuthenticationTokenFilter, JwtTokenUtil
推薦答案
我得到了你的要求.
- 您需要在請(qǐng)求標(biāo)頭(針對(duì)每個(gè)請(qǐng)求)中公開應(yīng)通過 JWT 令牌訪問的 API.
- Web 應(yīng)用程序也應(yīng)通過基于表單的身份驗(yàn)證機(jī)制進(jìn)行保護(hù),該機(jī)制應(yīng)基于 http 會(huì)話工作.
您可以通過兩個(gè)身份驗(yàn)證過濾器來實(shí)現(xiàn)這一點(diǎn).
You can achieve this by two authentication filters.
Filter - 1:用于 Rest API (JwtAuthTokenFilter),它應(yīng)該是無狀態(tài)的,并由每次在請(qǐng)求中發(fā)送的授權(quán)令牌標(biāo)識(shí).
Filter - 2:你需要另一個(gè)過濾器(UsernamePasswordAuthenticationFilter) 默認(rèn)情況下,如果你通過 http.formLogin()
配置它,spring-security 會(huì)提供這個(gè).這里每個(gè)請(qǐng)求都由關(guān)聯(lián)的會(huì)話(JSESSIONID
cookie)標(biāo)識(shí).如果請(qǐng)求不包含有效會(huì)話,那么它將被重定向到身份驗(yàn)證入口點(diǎn)(例如:登錄頁(yè)面).
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
創(chuàng)建兩個(gè)內(nèi)部靜態(tài)類,它們應(yīng)該擴(kuò)展
WebSecurityConfigurerAdapter
并使用@Configuration 和@Order 進(jìn)行注釋.這里rest api配置的順序應(yīng)該是1,Web應(yīng)用程序配置的順序應(yīng)該大于1Create 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請(qǐng)參閱 我在此鏈接中的回答了解更多詳情,其中有解釋深度與必要的代碼.如果需要,請(qǐng)隨時(shí)從 github 存儲(chǔ)庫(kù)獲取可下載鏈接.
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.
限制
在這里,兩個(gè)過濾器將并排工作(并行).我的意思是來自 Web 應(yīng)用程序,即使用戶通過會(huì)話進(jìn)行身份驗(yàn)證,他也無法在沒有 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.編輯
對(duì)于 OP 的要求,他不想定義任何角色,但允許經(jīng)過身份驗(yàn)證的用戶訪問 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
這篇關(guān)于Spring Security MultiHttpSecurity 配置使我可以執(zhí)行兩種類型的身份驗(yàn)證.JWT 令牌和會(huì)話 Cookie的文章就介紹到這了,希望我們推薦的答案對(duì)大家有所幫助,也希望大家多多支持html5模板網(wǎng)!
【網(wǎng)站聲明】本站部分內(nèi)容來源于互聯(lián)網(wǎng),旨在幫助大家更快的解決問題,如果有圖片或者內(nèi)容侵犯了您的權(quán)益,請(qǐng)聯(lián)系我們刪除處理,感謝您的支持!