I'm using Spring 4 with Spring Security, custom GenericFilterBean and AuthenticationProvider implementations. I have mostly secured URLs with the exception of a URL to create new session: /v2/session (e.g. login based on the username and password and returns Auth Token to be used in the subsequent requests that require authentication) configured as follows:
#Configuration
#ComponentScan(basePackages={"com.api.security"})
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private ApiAuthenticationProvider apiAuthenticationProvider;
#Autowired
private AuthTokenHeaderAuthenticationFilter authTokenHeaderAuthenticationFilter;
#Autowired
private AuthenticationEntryPoint apiAuthenticationEntryPoint;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(apiAuthenticationProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(authTokenHeaderAuthenticationFilter, BasicAuthenticationFilter.class) // Main auth filter
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.authorizeRequests()
.antMatchers(HttpMethod.POST, "/v2/session").permitAll()
.anyRequest().authenticated();
http.exceptionHandling()
.authenticationEntryPoint(apiAuthenticationEntryPoint);
}
}
The authTokenHeaderAuthenticationFilter runs on every request and gets Token from the request header:
/**
* Main Auth Filter. Always sets Security Context if the Auth token Header is not empty
*/
#Component
public class AuthTokenHeaderAuthenticationFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
final String token = ((HttpServletRequest) request).getHeader(RequestHeaders.AUTH_TOKEN_HEADER);
if (StringUtils.isEmpty(token)) {
chain.doFilter(request, response);
return;
}
try {
AuthenticationToken authRequest = new AuthenticationToken(token);
SecurityContextHolder.getContext().setAuthentication(authRequest);
}
} catch (AuthenticationException failed) {
SecurityContextHolder.clearContext();
return;
}
chain.doFilter(request, response); // continue down the chain
}
}
The custom apiAuthenticationProvider will try to authenticate all requests based on the token provided in the header and if authentication is unsuccessful - throws AccessException and client will receive HTTP 401 response:
#Component
public class ApiAuthenticationProvider implements AuthenticationProvider {
#Autowired
private remoteAuthService remoteAuthService;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
AuthenticationToken authRequest = (AuthenticationToken) authentication;
String identity = null;
try {
identity = remoteAuthService.getUserIdentityFromToken(authRequest.getToken());
} catch (AccessException e) {
throw new InvalidAuthTokenException("Cannot get user identity from the token", e);
}
return new AuthenticationToken(identity, authRequest.getToken(), getGrantedAuthorites());
}
}
This works perfectly fine for the requests that require authentication. This works fine for the /v2/session request without the Authentication Header in it. However, for the /v2/session request that has an expired Auth Token in the header (or in the cookie - not shown in the code samples; this may happen sometimes if the client didn't clear the headers or continues sending cookies with requests) the security context will be initialized and apiAuthenticationProvider will throw an exception and respond with HTTP 401 to the client.
Since /v2/session has been configured as
http.authorizeRequests()
.antMatchers(HttpMethod.POST, "/v2/session").permitAll()
I would expect Spring Security to determine that before calling ApiAuthenticationProvider.authenticate(). What should be the way for the filter or auth provider to ignore/not throw the exception for the URLs configured as permitAll()?
Spring security filters get triggered before the request authorisation checks are performed. For the authorisation checks to work, it is assumed that the request has been through the filters and the Spring security context has been set (or not, depending on whether authentication credentials have been passed in).
In your filter you have check that continues with the filter chain processing if the token is not there. Unfortunately, if it is, then it will be passed to your provider for authentication, which throws an exception because the token has expired thus you're getting the 401.
Your best bet is to bypass filter execution for the URLs that you consider public. You can either do this in the filter itself or in your configuration class. Add the following method to your SecurityConfig class:
#Override
public void configure(WebSecurity webSecurity) {
webSecurity.ignoring().antMatchers(HttpMethod.POST, "/v2/session");
}
What this will do, is bypass your AuthTokenHeaderAuthenticationFilter completely for POST /v2/sessions URL.
Related
I am using spring security with custom Authentication Provider using basic auth.
When I am trying to hit backend API GET call through postman it is working fine only when I make changes in username
Here is the problem statement - whenever I modify the user name then only custom authenticator provider works. once I added the correct username and password then it works but after that when I am making any changes in password (giving wrong password) always showing 200 success response. If I am making changes in username (giving wrong username) then only call to custom authenticator provider happened and getting 401 response.
Java Spring code
#Configuration
#EnableWebSecurity
#ComponentScan("com.authentication.service")
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter{
#Autowired
private AuthService authProvider;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception { http
.httpBasic().and().logout().clearAuthentication(true).and() .authorizeRequests()
.antMatchers("/index.html", "/", "/home", "/login", "/assets/**").permitAll()
.anyRequest().authenticated() .and() .csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()); }
}
#Service
public class AuthService implements AuthenticationProvider{
#Override
public Authentication authenticate(Authentication authentication)
throws org.springframework.security.core.AuthenticationException {
String userName = (String) authentication.getPrincipal();
String userPassword = authentication.getCredentials().toString();
if(authenticateUser(userName, userPassword)) {
return new UsernamePasswordAuthenticationToken(userName, userPassword, new ArrayList<>());
} else {
return null;
}
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
public Boolean authenticateUser(String userName, String userPassword) {
// Using some third party service
// return true if user is authenticated else false
}
}
This is occurring because Spring Security is creating a session that is returned as a Cookie upon successful authentication (You can check this in the Cookies panel in the Postman's response). For Further requests, even if you provide invalid credentials, Postman will send this session cookie that will be used for Authentication.
To remove this effect, you can update your session management policy to be SessionCreationPolicy.STATELESS, this will make sure no session is created by the application and the Basic Auth credentials that are sent in the request are used for authentication.
You can update the Session Management Policy like this:
#Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
I am developing a microservice ecosystem using spring-boot. The microservices which are in place at the moment :
Spring Cloud Gateway - Zuul (responsible also for authorization requests downstream for microservices - extracting tokens from requests and validates whether the user has the right role to perform requests),
SSO using spring security LDAP ( responsible for authenticate user and generate JWT tokens) , SSO has also just a login page using thymeleaf
Web interface using Thymeleaf without login page ( not sure if I should use here spring security, at the moment)
Another microservice which provides data to web ui based on request from the browser
Discovery services using Eureka
The idea is filtering all the requests on the gateway for validating and forward the requests. If the user is not authenticated or token is experied then forward the user to SSO for login.
The firewall will expose only the port on Gateway side then others one will be theirs ports blocked using firewall rules.
Now i am blocked without knowing where to go or if I should move the SSO together with the gateway ( conceptually wrong but it might be a workaround if i do not find any solution)
Following the issue : The user hits the gateway (ex. http://localhost:7070/web) then the gateway forward the user to (ex. http://localhost:8080/sso/login), after the credentials have been validated , the SSO creates the JWT tokens and add it to the Header of response.
Afterwards the SSO redirect the request back to the gateway (ex. http://localhost:7070/web).
Until here, everything works fine but when the request reaches the gateway there is no 'Authorization' header on request which means NO JWT token.
So the gateway should extract the token, check the credentials and forward the request to the Web interface (ex. http://localhost:9090)
I am aware that using Handler on SSO to redirect request won't work at all due to 'Redirect' from spring will remove the token from the header before redirect.
But I do not know whether there is another way to set again the JWT on the header after Spring has removed it from the request or not.
Is there any conceptually issue on the architecture side? How can I forward the JWT to the gateway for being checked?
SSO
#EnableWebSecurity
public class SecurityCredentialsConfig extends WebSecurityConfigurerAdapter {
#Value("${ldap.url}")
private String ldapUrl;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
// Stateless session; session won't be used to store user's state.
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.formLogin()
.loginPage("/login")
// Add a handler to add token in the response header and forward the response
.successHandler(jwtAuthenticationSuccessHandler())
.failureUrl("/login?error")
.permitAll()
.and()
// handle an authorized attempts
.exceptionHandling()
.accessDeniedPage("/login?error")
.and()
.authorizeRequests()
.antMatchers( "/dist/**", "/plugins/**").permitAll()
.anyRequest().authenticated();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.userDnPatterns("uid={0},ou=people")
.groupSearchBase("ou=groups")
.userSearchFilter("uid={0}")
.groupSearchBase("ou=groups")
.groupSearchFilter("uniqueMember={0}")
.contextSource()
.url(ldapUrl);
}
#Bean
public AuthenticationSuccessHandler jwtAuthenticationSuccessHandler() {
return new JwtAuthenticationSuccessHandler();
}
}
public class JwtAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
#Autowired
private JwtConfig jwtConfig;
#Autowired
private JwtTokenService jwtTokenService;
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication auth) throws IOException, ServletException {
String token = jwtTokenService.expiring(ImmutableMap.of(
"email", auth.getName(),
"authorities", auth.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.map(Object::toString)
.collect(Collectors.joining(","))));
response.addHeader(jwtConfig.getHeader(), jwtConfig.getPrefix() + token);
DefaultSavedRequest defaultSavedRequest = (DefaultSavedRequest) request.getSession().getAttribute("SPRING_SECURITY_SAVED_REQUEST");
if(defaultSavedRequest != null){
getRedirectStrategy().sendRedirect(request, response, defaultSavedRequest.getRedirectUrl());
}else{
getRedirectStrategy().sendRedirect(request, response, "http://localhost:7070/web");
}
}
}
Gateway
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtConfig jwtConfig;
#Value("${accessDeniedPage.url}")
private String accessDeniedUrl;
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.csrf().disable() // Disable CSRF (cross site request forgery)
// we use stateless session; session won't be used to store user's state.
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.formLogin()
.loginPage("/sso/login")
.permitAll()
.and()
// handle an authorized attempts
// If a user try to access a resource without having enough permissions
.exceptionHandling()
.accessDeniedPage(accessDeniedUrl)
//.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
.and()
// Add a filter to validate the tokens with every request
.addFilterBefore(new JwtTokenAuthenticationFilter(jwtConfig), UsernamePasswordAuthenticationFilter.class)
// authorization requests config
.authorizeRequests()
.antMatchers("/web/**").hasAuthority("ADMIN")
// Any other request must be authenticated
.anyRequest().authenticated();
}
}
#RequiredArgsConstructor
public class JwtTokenAuthenticationFilter extends OncePerRequestFilter {
private final JwtConfig jwtConfig;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
// 1. get the authentication header. Tokens are supposed to be passed in the authentication header
String header = request.getHeader(jwtConfig.getHeader());
// 2. validate the header and check the prefix
if(header == null || !header.startsWith(jwtConfig.getPrefix())) {
chain.doFilter(request, response); // If not valid, go to the next filter.
return;
}
// If there is no token provided and hence the user won't be authenticated.
// It's Ok. Maybe the user accessing a public path or asking for a token.
// All secured paths that needs a token are already defined and secured in config class.
// And If user tried to access without access token, then he/she won't be authenticated and an exception will be thrown.
// 3. Get the token
String token = header.replace(jwtConfig.getPrefix(), "");
try { // exceptions might be thrown in creating the claims if for example the token is expired
// 4. Validate the token
Claims claims = Jwts.parser()
.setSigningKey(jwtConfig.getSecret().getBytes())
.parseClaimsJws(token)
.getBody();
String email = claims.get("email").toString();
if(email != null) {
String[] authorities = ((String) claims.get("authorities")).split(",");
final List<String> listAuthorities = Arrays.stream(authorities).collect(Collectors.toList());
// 5. Create auth object
// UsernamePasswordAuthenticationToken: A built-in object, used by spring to represent the current authenticated / being authenticated user.
// It needs a list of authorities, which has type of GrantedAuthority interface, where SimpleGrantedAuthority is an implementation of that interface
final UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
email, null, listAuthorities
.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList()));
// 6. Authenticate the user
// Now, user is authenticated
SecurityContextHolder.getContext().setAuthentication(auth);
}
} catch (Exception e) {
// In case of failure. Make sure it's clear; so guarantee user won't be authenticated
SecurityContextHolder.clearContext();
}
// go to the next filter in the filter chain
chain.doFilter(request, response);
}
}
#Component
public class AuthenticatedFilter extends ZuulFilter {
#Override
public String filterType() {
return PRE_TYPE;
}
#Override
public int filterOrder() {
return 0;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() throws ZuulException {
final Object object = SecurityContextHolder.getContext().getAuthentication();
if (object == null || !(object instanceof UsernamePasswordAuthenticationToken)) {
return null;
}
final UsernamePasswordAuthenticationToken user = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
final RequestContext requestContext = RequestContext.getCurrentContext();
/*
final AuthenticationDto authenticationDto = new AuthenticationDto();
authenticationDto.setEmail(user.getPrincipal().toString());
authenticationDto.setAuthenticated(true);
authenticationDto.setRoles(user.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList())); */
try {
//requestContext.addZuulRequestHeader(HttpHeaders.AUTHORIZATION, (new ObjectMapper()).writeValueAsString(authenticationDto));
requestContext.addZuulRequestHeader(HttpHeaders.AUTHORIZATION, (new ObjectMapper()).writeValueAsString("authenticationDto"));
} catch (JsonProcessingException e) {
throw new ZuulException("Error on JSON processing", 500, "Parsing JSON");
}
return null;
}
}
There is an issue about JWT. It is called "Logout Problem". First you need to understand what it is.
Then, check TokenRelay filter (TokenRelayGatewayFilterFactory) which is responsible for passing authorization header to downstream.
If you look at that filter, you will see that JWTs are stored in ConcurrentHashMap (InMemoryReactiveOAuth2AuthorizedClientService). The key is session, the value is JWT. So, session-id is returned instead of JWT header as the response provided.
Until here, everything works fine but when the request reaches the
gateway there is no 'Authorization' header on request which means NO
JWT token.
Yes. When the request comes to gateway, TokenRelay filter takes session-id from request and find JWT from ConcurrentHashMap, then it passes to Authorization header during downstream.
Probably, this flow is designed by spring security team to address JWT logout problem.
I am using okta to do authentication. Our company's okta disabled the 'default' authorization server. So right now I cannot use 'okta-spring-security-starter' to simple do this to verify token passed from url headers:
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
#Configuration
public class OktaOAuth2WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/health").permitAll()
.anyRequest().authenticated()
.and()
.oauth2ResourceServer().jwt();
http.cors();
Okta.configureResourceServer401ResponseBody(http);
}
}
So I need to hit okta introspect endpoint (https://developer.okta.com/docs/reference/api/oidc/#introspect) to verify. So I am wondering can I integrate this procedure within the config of WebSecurityConfigurerAdapter. maybe something like this???:
import com.okta.spring.boot.oauth.Okta;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
#Configuration
public class OktaOAuth2WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/health").permitAll()
.anyRequest().authenticated()
.and()
/*add something there*/
http.cors();
}
}
I saw something like override AuthenticationProvider(Custom Authentication provider with Spring Security and Java Config), and use httpbasic auth. Can I do similiar thing if I use .oauth2ResourceServer().jwt().
My idea is override the authentication provider and in the provider, hit the okta introspect endpoint, will this work???
Spring Security 5.2 ships with support for introspection endpoints. Please take a look at the Opaque Token sample in the GitHub repo.
To answer briefly here, though, you can do:
http
.authorizeRequests(authz -> authz
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.opaqueToken(opaque -> opaque
.introspectionUri("the-endpoint")
.introspectionClientCredentials("client-id", "client-password")
)
);
If you are using Spring Boot, then it's a bit simpler. You can provide those properties in your application.yml:
spring:
security:
oauth2:
resourceserver:
opaquetoken:
introspection-uri: ...
client-id: ...
client-secret: ...
And then your DSL can just specify opaqueToken:
http
.authorizeRequests(authz -> authz
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.opaqueToken(opaque -> {})
);
I don't use Okta thus I don't know how exactly it works. But I have 2 assumptions:
Every request contains an accessToken in the Authorization header
You make a POST request to ${baseUrl}/v1/introspect and it will answer you with true or false to indicate that accessToken is valid or not
With these 2 assumptions in mind, if I have to manually implement custom security logic authentication, I would do following steps:
Register and implement a CustomAuthenticationProvider
Add a filter to extract access token from request
Registering custom authentication provider:
// In OktaOAuth2WebSecurityConfig.java
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticationProvider());
}
#Bean
CustomAuthenticationProvider customAuthenticationProvider(){
return new CustomAuthenticationProvider();
}
CustomAuthenticationProvider:
public class CustomAuthenticationProvider implements AuthenticationProvider {
private static final Logger logger = LoggerFactory.getLogger(CustomAuthenticationProvider.class);
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
logger.debug("Authenticating authenticationToken");
OktaTokenAuthenticationToken auth = (OktaTokenAuthenticationToken) authentication;
String accessToken = auth.getToken();
// You should make a POST request to ${oktaBaseUrl}/v1/introspect
// to determine if the access token is good or bad
// I just put a dummy if here
if ("ThanhLoyal".equals(accessToken)){
List<GrantedAuthority> authorities = Collections.singletonList(new SimpleGrantedAuthority("USER"));
logger.debug("Good access token");
return new UsernamePasswordAuthenticationToken(auth.getPrincipal(), "[ProtectedPassword]", authorities);
}
logger.debug("Bad access token");
return null;
}
#Override
public boolean supports(Class<?> clazz) {
return clazz == OktaTokenAuthenticationToken.class;
}
}
To register the filter to extract accessToken from request:
// Still in OktaOAuth2WebSecurityConfig.java
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterAfter(accessTokenExtractorFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests().anyRequest().authenticated();
// And other configurations
}
#Bean
AccessTokenExtractorFilter accessTokenExtractorFilter(){
return new AccessTokenExtractorFilter();
}
And the filter it self:
public class AccessTokenExtractorFilter extends OncePerRequestFilter {
private static final Logger logger = LoggerFactory.getLogger(AccessTokenExtractorFilter.class);
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
logger.debug("Filtering request");
Authentication authentication = getAuthentication(request);
if (authentication == null){
logger.debug("Continuing filtering process without an authentication");
filterChain.doFilter(request, response);
} else {
logger.debug("Now set authentication on the request");
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
}
}
private Authentication getAuthentication(HttpServletRequest request) {
String accessToken = request.getHeader("Authorization");
if (accessToken != null){
logger.debug("An access token found in request header");
List<GrantedAuthority> authorities = Collections.singletonList(new SimpleGrantedAuthority("USER"));
return new OktaTokenAuthenticationToken(accessToken, authorities);
}
logger.debug("No access token found in request header");
return null;
}
}
I have uploaded a simple project here for your easy reference: https://github.com/MrLoyal/spring-security-custom-authentication
How it works:
The AccessTokenExtractorFilter is placed right after the UsernamePasswordAuthenticationFilter, which is a default filter by Spring Security
A request arrives, the above filter extracts accessToken from it and place it in the SecurityContext
Later, the AuthenticationManager calls the AuthenticationProvider(s) to authenticate request. This case, the CustomAuthenticationProvider is invoked
BTW, your question should contain spring-security tag.
Update 1: About AuthenticationEntryPoint
An AuthenticationEntryPoint declares what to do when an unauthenticated request arrives ( in our case, what to do when the request does not contain a valid "Authorization" header).
In my REST API, I simply response 401 HTTP status code to client.
// CustomAuthenticationEntryPoint
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.reset();
response.setStatus(401);
// A utility method to add CORS headers to the response
SecUtil.writeCorsHeaders(request, response);
}
Spring's LoginUrlAuthenticationEntryPoint redirects user to login page if one is configured.
So if you want to redirect unauthenticated requests to Okta's login page, you may use a AuthenticationEntryPoint.
I'm using spring basic authentication with a custom authentication provider:
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthenticationProvider authProvider;
#Override
protected void configure(
AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated()
.and()
.httpBasic();
}
And
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String name = authentication.getName();
String password = authentication.getCredentials().toString();
if (customauth()) { // use the credentials
// and authenticate against the third-party system
{
return new UsernamePasswordAuthenticationToken(
name, password, new ArrayList<>());
}
} else {
return null;
}
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(
UsernamePasswordAuthenticationToken.class
);
}
To test this I'm using postman with the following tests:
invalid credentials -> 401 unauthorized
correct credentials -> 200 OK
invalid credentials -> 200 OK
My problem is that the last request should return 401 unauthorized and every following request after a successful login is 200 OK even with a wrong token and without token.
Thanks in advance.
When you logged in successfully, Spring Security will create an Authentication object and will put it in SecurityContext in your HTTP session. As far as you have a valid session with a valid Authentication object at the server, Spring Security won't authenticate your request again and will use the Authentication object saved in your session.
This is a Spring Security feature, see SEC-53:
Check the SecurityContextHolder for an authenticated Authentication and reuse it in that case, do not call the authentication manager again.
If you like to reauthenticate, you could
use no session at all
logout before reauthenticate
In both cases Spring Security will not find an authenticated user saved in the session and will use the new username and password for authentication.
I have POST request with something like this in my Spring project:
{"clientKey":"XXX", "accessKey":"ZZZ", ... }
My backend works in very simple paradigm: get clientKey (login) and accessKey (password) params from POST body, check their persistence in DB and then do some business logic.
I need to implement minimal security check logic using Spring Security for each incoming request (without sessions and tokens).
SecurityConfig.java
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/**").hasRole("USER")
.and().csrf().disable();
http.addFilterBefore(new ApiAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
ApiAuthorizationFilter.java
public class ApiAuthorizationFilter extends UsernamePasswordAuthenticationFilter {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
//
// always prints "{}", why?
//
Logger.getLogger("test").log(Level.INFO, request.getParameterMap().toString());
//
// Ok, I will make some manual auth operations for testing purposes.
// Seems what it isn't work too..
//
Set<SimpleGrantedAuthority> authorities = new HashSet<>(1);
authorities.add(new SimpleGrantedAuthority("USER"));
Authentication auth = new UsernamePasswordAuthenticationToken(
"94fc97a7b3fd2175472ec4a41bcb3b14",
"746b2aa32fe90f0ba53e6efe7a8d1f1f",
authorities);
SecurityContextHolder.getContext().setAuthentication(auth);
chain.doFilter(request, response);
}
}
What I doing wrong? Does UsernamePasswordAuthenticationFilter work only with login forms when submitted or I need another filter in security chain?