У меня есть API отдыха, который я хочу защитить с помощью Spring Security. Я получаю jwt с моего сервера аутентификации, но когда я нажимаю rest api, он отвечает запрещенным для операции POST / DELETE и прекрасно работает для операций GET. Мои классы конфигурации:
package com.mycompany.restapi.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
@Configuration
public class RestAPISecurityConfig extends WebSecurityConfigurerAdapter {
private final String publicKeyValue;
private final UserDetailsService userDetailsService;
@Autowired
public RestAPISecurityConfig(@Value("${environment.publicKey}") String publicKeyValue, UserDetailsService userDetailsService) {
this.publicKeyValue = publicKeyValue;
this.userDetailsService = userDetailsService;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers(getPermitAllMatchers()).permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JwtAuthorizationFilter(authenticationManager(), publicKeyValue, userDetailsService))
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
/*
Array of all paths that should be accessible without authentication/authorization
*/
public static String[] getPermitAllMatchers() {
return new String[] {
"/user/forgetPassword", // URL that is needed to generate the forgot password mail
"/user/resetPassword", // URL where the one time key is posted to, to set a new password
"/"
};
}
}
и фильтр:
package com.blupa.hb.restapi.security;
import com.mycompany.user.HBUserDetails;
import com.mycompany.user.User;
import com.mycompany.user.UserRepository;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.SecurityException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import java.util.stream.Collectors;
public class JwtAuthorizationFilter extends BasicAuthenticationFilter {
private static final Logger log = LoggerFactory.getLogger(JwtAuthorizationFilter.class);
private final PublicKey publicKey;
private final UserDetailsService userDetailsService;
public JwtAuthorizationFilter(AuthenticationManager authenticationManager, String publicKeyValue, UserDetailsService userDetailsService) {
super(authenticationManager);
this.publicKey = getKey(publicKeyValue);
this.userDetailsService = userDetailsService;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
// Is requested path accessible to everyone? -> Don't check the access token, i.e. continue the filter chain
String requestedPath = request.getServletPath();
String[] publicPaths = RestAPISecurityConfig.getPermitAllMatchers();
//
boolean isPublicPath = Arrays.asList(publicPaths).contains(requestedPath);
if (isPublicPath) {
chain.doFilter(request, response);
return;
}
// Check Access Token
UsernamePasswordAuthenticationToken authentication = getAuthentication(request);
if (authentication == null) {
chain.doFilter(request, response);
return;
}
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
String token = request.getHeader("Authorization");
if (token != null && !token.isEmpty() && token.startsWith("Bearer")) {
try {
Jws<Claims> parsedToken = Jwts.parser()
.setSigningKey(publicKey)
.parseClaimsJws(token.replace("Bearer ", ""));
// Check for the "ati" attribute in the jwt
// Only refresh tokens have it, so we should not grant access for it
String refreshTokenAti = (String) parsedToken.getBody().get("ati");
if (refreshTokenAti != null) {
return null;
}
String username = (String) parsedToken
.getBody()
.get("user_name");
List<GrantedAuthority> authorities = ((List<?>) parsedToken.getBody().get("authorities"))
.stream()
.map(authority -> new SimpleGrantedAuthority((String) authority))
.collect(Collectors.toList());
if (!username.isEmpty()) {
UserDetails user = userDetailsService.loadUserByUsername(username);
return new UsernamePasswordAuthenticationToken(user, null, authorities);
}
} catch (ExpiredJwtException exception) {
log.warn("Request to parse expired JWT : {} failed : {}", token, exception.getMessage());
} catch (UnsupportedJwtException exception) {
log.warn("Request to parse unsupported JWT : {} failed : {}", token, exception.getMessage());
} catch (MalformedJwtException exception) {
log.warn("Request to parse invalid JWT : {} failed : {}", token, exception.getMessage());
} catch (SecurityException exception) {
log.warn("Request to parse JWT with invalid signature : {} failed : {}", token, exception.getMessage());
} catch (IllegalArgumentException exception) {
log.warn("Request to parse empty or null JWT : {} failed : {}", token, exception.getMessage());
}
}
return null;
}
// create PublicKey instance from publicKey string : https://stackoverflow.com/a/24502245
public static PublicKey getKey(String key){
try{
String parsedKey = key.replaceAll("\\n", "").replace("-----BEGIN PUBLIC KEY-----", "").replace("-----END PUBLIC KEY-----", "");
byte[] parsedBytes = Base64.getDecoder().decode(parsedKey);
X509EncodedKeySpec X509publicKey = new X509EncodedKeySpec(parsedBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePublic(X509publicKey);
}
catch(Exception e){
e.printStackTrace();
}
return null;
}
}
Я использую открытый ключ, сгенерированный в классе фильтра. когда я нажимаю на запрос get, он работает нормально, но не работает для запроса POST / DELETE.