Spring Security java configuration confusion - java

Why the login isn't prompted with following configuration? When I try to access /public/user, I get error 403 (access denied). However, if I uncomment those commented lines at WebServiceSecurityConfiguration.configure, I got redirected to login page, as desired. Why those lines are needed for from-login being properly configured, as the antMatcher matches different path in the first place. I guess there is some conflict, which misconfigures the AuthenticationEntryPoint, but I don't really have idea how that happens. What I'm trying to achieve is configuring two security chains, one for login path to obtain the JWT token, and another for web services to authenticate against the token. Everything works perfectly with those lines uncommented, but I noticed by accident form-login stopped working without them, and am super confused why is that.
#Configuration
#Profile("javasecurity")
#Order(11)
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private TokenHandler tokenHandler;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("password").authorities(new SimpleGrantedAuthority("ROLE_USER")).and()
.withUser("admin").password("password").authorities(
new SimpleGrantedAuthority("ROLE_USER"),
new SimpleGrantedAuthority("ROLE_ADMIN")).and()
.withUser("guest").password("guest").authorities(new SimpleGrantedAuthority("ROLE_GUEST"));
}
#Override
#Bean
public UserDetailsService userDetailsServiceBean() throws Exception {
return super.userDetailsServiceBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/public/**")
.permitAll()
.and()
.formLogin()
.successHandler(authenticationSuccessHandler())
.and()
.logout();
}
#Bean
public AuthenticationSuccessHandler authenticationSuccessHandler() {
return new AuthenticationSuccessHandler() {
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
tokenHandler.setToken(response, authentication.getName());
response.getWriter().println("User authenticated and cookie sent");
response.flushBuffer();
}
};
}
#Configuration
#Profile("javasecurity")
#Order(10)
public static class WebServiceSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private TestAuthenticationFilter testAuthenticationFilter;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/secured/**")
.authenticated();
// .and()
// .antMatcher("/secured/**")
// .securityContext().securityContextRepository(new NullSecurityContextRepository())
// .and()
// .addFilterAt(testAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
}
--
#Component("TestAuthenticationFilter")
public class TestAuthenticationFilter extends GenericFilterBean {
#Autowired
private TokenHandler tokenHandler;
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("TestAuthenticationFilter doFitler");
attemptAuthentication((HttpServletRequest) request);
chain.doFilter(request, response);
clearAuthentication();
System.out.println("doFitler end");
}
public void attemptAuthentication(HttpServletRequest request) {
try {
UserDetails user = tokenHandler.loadUserFromToken(request);
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(user, user.getPassword());
SecurityContextHolder.getContext().setAuthentication(auth);
} catch (Exception e) {
// Do nothing
}
}
public void clearAuthentication() {
SecurityContextHolder.getContext().setAuthentication(null);
}
#Configuration
public static class DisableFilterRegistration {
#Autowired
private TestAuthenticationFilter filter;
#Bean
public FilterRegistrationBean disablerBean() {
FilterRegistrationBean bean = new FilterRegistrationBean(filter);
bean.setEnabled(false);
return bean;
}
}
}
--
#Component("TokenHandler")
public class TokenHandler {
#Autowired(required = false)
private UserDetailsService userDetailsService;
public void setToken(HttpServletResponse response, String username) {
response.addCookie(new Cookie("user", username));
}
public UserDetails loadUserFromToken(HttpServletRequest request) throws BadCredentialsException {
Cookie[] cookies = request.getCookies();
Cookie token = null;
for (Cookie c : cookies) {
if (c.getName().equals("user")) {
token = c;
break;
}
}
if (token == null)
return null;
else
return userDetailsService.loadUserByUsername(token.getValue());
}
}
--
#RestController
#RequestMapping("/public")
public class PublicController {
#GetMapping("/norole")
public String noRole() {
return "no role";
}
#GetMapping("/user")
#PreAuthorize("hasRole('ROLE_USER')")
public String roleUser() {
return "role_user";
}
}
--
#RestController
#RequestMapping("/secured")
public class SecuredController {
#GetMapping("/user")
#PreAuthorize("hasRole('ROLE_USER')")
public String roleUser() {
return "role_user";
}
#GetMapping("/admin")
#PreAuthorize("hasRole('ROLE_ADMIN')")
public String roleAdmin() {
return "role_admin";
}
#GetMapping("/norole")
public String noRole() {
return "no role";
}
}

Login-from got functional again after declaring adding
http.antMatcher("/secured/**")
As the first call in the WebServiceSecurityConfiguration.configure. Does that mean that without it the configuration negates the form-login, which is configured after this particular configuration? Also, it seems like the position of antMatcher can be arbitrary, is this the case? Could someone explain what is actually happening there?

Related

combining mutiple security configurations

I am currently working on a spring boot project that has multiple security (authentication) configurations combined:
a rest api with http basic auth
a rest api with jwt auth.
a web (form login) with 2fa auth.
The problem I am experiencing is, that the configurations cannot be entirely seperated. More specific: The authenticiation providers are accumulated (in the provider manager), which prevents my preferred setup from working correctly.
What happens is this: In config (3) i have a custom 2fa authentication provider which checks if the credentials (both password and 2FA code!) are entered correctly. If the password is correct, but the 2fa code is not, it exits (catches) with a (authentication or bad credentials) exception. However, as it exits the 2fa authentication provider, it goes back to the provider manager. The latter has another (DAO) auth provider at hand, and checks the credentials again: but this time: only password! not the 2fa code!). As the password is ok, it authorized the request.
So for short: I am experiencing a problem where the first authentication provider does NOT authorize, but the second does (because that one does not take the 2fa code into account)!
I have been at this for days, and i cannot seem to get this right.
As an alternative i have now opted for a solution using a 2fa custom filter. But is not my preferred solution, as it gives me some frontend problems (i first have to authorize the username/password, and only after that i can check the 2fa code).
Is there a solution using my 2fa auth provider? I would sort of wish that the auth providers would not get accumulated, so that when the first auth. provider exits with bad credentials, the auth procedure ends with a 'bad credentials'.
My config class:
WebSecurityConfig.java
#EnableWebSecurity
#Configuration
#AllArgsConstructor
public class WebSecurityConfig_backup extends WebSecurityConfigurerAdapter {
private UserDetailsService userDetailsService;
private PasswordEncoder passwordEncoder;
private HttpBasicAuthenticationExceptionHandler authenticationExceptionHandler;
private JwtAuthenticationProvider jwtAuthenticationProvider;
private MfaAuthenticationDetailsSource mfaAuthenticationDetailsSource;
private MfaAuthenticationProvider2 mfaAuthenticationProvider;
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Configuration
#Order(1)
public class ApiV1WebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/v1/**")
.httpBasic()
.authenticationEntryPoint(authenticationExceptionHandler)
.and()
.csrf().disable()
.authorizeRequests(authorize -> {
authorize
.mvcMatchers(HttpMethod.GET,
"/api/v1/transaction/**").hasAnyRole("BANK", "ADMIN")
.mvcMatchers(HttpMethod.POST,
"/api/v1/transaction/**").hasAnyRole("BANK", "ADMIN");
//.anyRequest().denyAll();
})
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth .userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
}
#Configuration
#Order(2)
public class ApiV2WebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/v2/**")
.addFilter(bearerTokenAuthenticationFilter())
.addFilter(credentialsAuthenticationFilter())
.csrf().disable()
.authorizeRequests(authorize -> {
authorize
.mvcMatchers(HttpMethod.GET,
"/api/v2/transaction/**").hasAnyRole("BANK", "ADMIN")
.mvcMatchers(HttpMethod.POST,
"/api/v2/transaction/**").hasAnyRole("BANK", "ADMIN");
//.anyRequest().denyAll();
})
.authorizeRequests()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
private BearerTokenAuthenticationFilter bearerTokenAuthenticationFilter() throws Exception {
BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(authenticationManager());
filter.setAuthenticationFailureHandler(authenticationFailureHandler());
return filter;
}
private CredentialsAuthenticationFilter credentialsAuthenticationFilter() throws Exception {
CredentialsAuthenticationFilter filter = new CredentialsAuthenticationFilter(authenticationManager());
filter.setAuthenticationFailureHandler(authenticationFailureHandler());
return filter;
}
private AuthenticationFailureHandler authenticationFailureHandler() {
return (request, response, ex) -> {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType(MediaType.APPLICATION_JSON.toString());
response.setCharacterEncoding(StandardCharsets.UTF_8.displayName());
ResponseWriterUtil.writeErrorResponse(response, ex.getMessage());
};
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(jwtAuthenticationProvider)
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
}
#Configuration
#Order(3)
public class FormWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
//authorisation
http//.addFilterBefore(googleTfaFilter, SessionManagementFilter.class)
.authorizeRequests(authorize -> {
authorize
.mvcMatchers("/", "/login", "/logout", "/registrationPage", "/register").permitAll()
.mvcMatchers(HttpMethod.GET,
"/others1", "/others2").hasAnyRole("USER", "ADMIN")
.mvcMatchers(HttpMethod.POST,
"/others1", "/others2").hasAnyRole("USER", "ADMIN");
})
.formLogin()
.loginPage("/login").permitAll()
.usernameParameter("email")
.loginProcessingUrl("/authenticate")
.defaultSuccessUrl("/")
.failureUrl("/login?error")
.authenticationDetailsSource(mfaAuthenticationDetailsSource)
.and()
.logout()
.deleteCookies("JSESSIONID")
.logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET"))
.logoutSuccessUrl("/login?logout").permitAll()
.and()
.sessionManagement()
.sessionFixation().migrateSession()
.and()
.headers().frameOptions().sameOrigin()
.and()
.csrf();
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**", "/js/**", "/img/**", "/lib/**");
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(mfaAuthenticationProvider)
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
}
}
auth provider for config 2:
#Slf4j
#RequiredArgsConstructor
#Component
public class JwtAuthenticationProvider implements AuthenticationProvider {
private final UserDetailsService userDetailsService;
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(BearerTokenAuthenticationToken.class);
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
BearerTokenAuthenticationToken bearerToken = (BearerTokenAuthenticationToken) authentication;
Authentication auth = null;
try {
//validate the token
Jwts.parser().setSigningKey(JWT_TOKEN_SECRET).parseClaimsJws(bearerToken.getToken());
JwtTokenUtil jwtTokenUtil = new JwtTokenUtil(bearerToken.getToken());
String username = jwtTokenUtil.getUsernameFromToken();
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
auth = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
log.debug("Authentication token: " + auth);
} catch (IllegalArgumentException e) {
throw new UserServiceAuthenticationException("Invalid token");
} catch (ExpiredJwtException e) {
throw new UserServiceAuthenticationException("Token expired");
} catch (SignatureException e) {
throw new UserServiceAuthenticationException("Invalid signature");
}
return auth;
}
}
Auth provider for config (3)
#Slf4j
#AllArgsConstructor
#Component
public class MfaAuthenticationProvider2 implements AuthenticationProvider {
private UserRepo userRepository;
private GoogleAuthenticator googleAuthenticator;
private PasswordEncoder passwordEncoder;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
String verficationCode = ((MfaAuthenticationDetails) authentication.getDetails()).getUserMFaCode();
User user = userRepository.findByEmail(authentication.getName()).stream().findFirst().orElse(null);
if(user == null || !passwordEncoder.matches(password, user.getPassword())){
throw new BadCredentialsException("Invalid username or password");
}
try {
if(!googleAuthenticator.authorizeUser(user.getUsername(), Integer.parseInt(verficationCode))){
throw new BadCredentialsException("Invalid verification code.");
}
} catch (Exception e) {
throw new BadCredentialsException("Authentication failed. Please try again.");
}
return new UsernamePasswordAuthenticationToken(user, password, user.getAuthorities());
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
My current alternative solution for config 3:
#Slf4j
#Component
public class GoogleTfaFilter2 extends OncePerRequestFilter {
private final AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
private final GoogleTfaFailureHandler googleTfaFailureHandler = new GoogleTfaFailureHandler();
private final RequestMatcher urlIs2fa = new AntPathRequestMatcher("/verify2fa");
private final RequestMatcher urlIs2fa2 = new AntPathRequestMatcher("/register2fa");
private final RequestMatcher urlResource = new AntPathRequestMatcher("/resources/**");
private final RequestMatcher api = new AntPathRequestMatcher("/api/**");
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
StaticResourceRequest.StaticResourceRequestMatcher staticResourceRequestMatcher =
PathRequest.toStaticResources().atCommonLocations();
if (urlIs2fa.matches(request) || urlResource.matches(request) || urlIs2fa2.matches(request)||
staticResourceRequestMatcher.matcher(request).isMatch() || api.matches(request)) {
filterChain.doFilter(request, response);
return;
}
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && !authenticationTrustResolver.isAnonymous(authentication)){
log.debug("Processing 2FA Filter");
if (authentication.getPrincipal() != null && authentication.getPrincipal() instanceof User) {
User user = (User) authentication.getPrincipal();
if (!user.getMfaPassed()) {
log.debug("2FA Required");
request.getRequestDispatcher("/verify2fa").forward(request, response);
return;
}
}
}
filterChain.doFilter(request, response);
}
}

Java spring boot basic authentication with redis

I'm trying to add authentication layer into my spring boot application, and all the tutorials i can find are with some mock user, such as:
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("learn").password("share").roles("USER");
}
I would like to implement something like this:
String credentials = request.getHeader('Authorization');
String userName = fetchUserName(credentials);
String password = fetchUserPassword(credentials);
String actualPassword = redis.getPassowrdForUser(userName);
if (actualPassword == someHash(password)) {
// continue with the request
} else {
// return unauthorised request 401
}
Thanks.
This is my current implementation, from some tutorial i've found:
#EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private BasicAuthenticationPoint authEntryPoint;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/user/signup").permitAll()
.anyRequest().authenticated();
http.httpBasic().authenticationEntryPoint(authEntryPoint);
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("learn").password("share").roles("USER");
}
#Bean
public static NoOpPasswordEncoder passwordEncoder() {
return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
}
}
#Component
public class BasicAuthenticationPoint extends BasicAuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authEx)
throws IOException, ServletException {
response.addHeader("WWW-Authenticate", "Basic realm=" +getRealmName());
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
PrintWriter writer = response.getWriter();
writer.println("HTTP Status 401 - " + authEx.getMessage());
}
#Override
public void afterPropertiesSet() throws Exception {
setRealmName("learn");
super.afterPropertiesSet();
}
}

Understanding jwt and login logic

I am new to jwt and im using some resources from the net to help me get to understand jwt properly , I am able now to generate a token and access a resource that requires authorisation, first , i have a controller like
#RequestMapping("/token")
public class TokenController {
private JwtGenerator jwtGenerator;
public TokenController(JwtGenerator jwtGenerator) {
this.jwtGenerator = jwtGenerator;
}
#PostMapping
public String generate(#RequestBody final JwtUser jwtUser) {
return jwtGenerator.generate(jwtUser);
}
excuse me for i will be posting a lot of code.I am using postman for testing
so when i pass this as a post
{
"useNe" : "ter",
"paord":"123",
"role":"ain"
} or
{
"username" : "ter",
"is":"123",
"role":"admin"
}
I am generating a token ,it should require a username and password i think before a jwt token should be produced and i want to implement a proper login ,below is the security config and other classes that i have
#EnableWebSecurity
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationProvider authenticationProvider;
#Autowired
private JwtAuthenticationEntryPoint entryPoint;
#Bean
public AuthenticationManager authenticationManagerBean() {
return new ProviderManager(Collections.singletonList(authenticationProvider));
}
#Bean
public JwtAuthenticationTokenFilter authenticationTokenFilter() throws Exception {
JwtAuthenticationTokenFilter filter = new JwtAuthenticationTokenFilter();
filter.setAuthenticationManager(authenticationManager());
// we set success handler so that we overide the default redirection
filter.setAuthenticationSuccessHandler(new JwtSuccessHandler());
return filter;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests().antMatchers("**/rest/**").authenticated()
.and()
.exceptionHandling().authenticationEntryPoint(entryPoint)
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(authenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
http.headers().cacheControl();
}
}
how can i create a login that will generate a token , or is that not the standard for jwt , also i want to have two types of role user and admin, admin can access all resources while user can access some , here are other classes
public class JwtAuthenticationTokenFilter extends AbstractAuthenticationProcessingFilter {
public JwtAuthenticationTokenFilter() {
super("/rest/**");
}
#Override
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse)
throws AuthenticationException, IOException, ServletException {
String header = httpServletRequest.getHeader("Authorisation");
if (header == null || !header.startsWith("Token")) {
throw new RuntimeException("JWT Token is missing");
}
String authenticationToken = header.substring(6);
JwtAuthenticationToken token = new JwtAuthenticationToken(authenticationToken);
return getAuthenticationManager().authenticate(token);
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult)
throws IOException, ServletException {
super.successfulAuthentication(request, response, chain, authResult);
chain.doFilter(request, response);
}
}
And my jwt generator should this not require a an username and password for login
#Component
public class JwtGenerator {
public String generate(JwtUser jwtUser) {
Claims claims = Jwts.claims()
.setSubject(jwtUser.getUserName());
claims.put("userId", String.valueOf(jwtUser.getId()));
claims.put("role", jwtUser.getRole());
return Jwts.builder()
.setClaims(claims)
.signWith(SignatureAlgorithm.HS512, "youtube")
.compact();
}
}
#Component
public class JwtAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
#Autowired
private JwtValidator validator;
#Override
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken)
throws AuthenticationException {
}
#Override
protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken)
throws AuthenticationException {
JwtAuthenticationToken jwtAuthenticationToken = (JwtAuthenticationToken) usernamePasswordAuthenticationToken;
String token = jwtAuthenticationToken.getToken();
JwtUser jwtUser = validator.validate(token);
if(jwtUser == null){
throw new RuntimeException("user token is incorrect" );
}
List<GrantedAuthority> grantedAuthorities = AuthorityUtils
.commaSeparatedStringToAuthorityList(jwtUser.getRole());
//we return an authenticated user
return new JwtUserDetails(jwtUser.getUserName(),jwtUser.getId(),token , grantedAuthorities);
}
#Override
public boolean supports(Class<?> aClass) {
return (JwtAuthenticationToken.class.isAssignableFrom(aClass));
}
}
How do i go about improving on this and end up with a proper login that generates a jwt and keeps it in headers for every request

Spring token security with credential security (springboot)

I have a question regarding security implementation on my server. I am making a SpringBoot application which has a control panel like website on it, where 1 single admin inputs needed data and i have managed to secure that part fine like this :
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/*").authorizeRequests().anyRequest().hasRole("ADMIN")
.and().formLogin().loginPage("/login.jsp")
.failureUrl("/login.jsp?error=1").loginProcessingUrl("/login")
.permitAll().and().logout()
.logoutSuccessUrl("/login.jsp");
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// Create a default account
auth.inMemoryAuthentication()
.withUser("admin")
.password("admin")
.roles("ADMIN");
}
Every website url is on /*, and that works fine. The next thing i need to do is to retrieve data from my mobile app and it needs to be secure. urls that the app should use is /rest/**. I have a Student class that stores email(username) and password that is created by that admin on web site. As far as i've read i need token implementation.
How can I implement token authentication?
To implement token based authentication for a mobile app, with Spring Boot and Spring Security.
Create a TokenAuthenticationFilter
public class TokenAuthenticationFilter extends GenericFilterBean {
private AuthenticationManager authenticationManager;
public TokenAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String apiKey = httpRequest.getHeader("API-Key");
String token = httpRequest.getHeader("Access-Token");
try {
if (!StringUtils.isEmpty(apiKey)) {
processTokenAuthentication(apiKey);
}
chain.doFilter(request, response);
} catch (InternalAuthenticationServiceException internalAuthenticationServiceException)
{
SecurityContextHolder.clearContext();
logger.error("Internal authentication service exception", internalAuthenticationServiceException);
httpResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
catch(AuthenticationException authenticationException)
{
SecurityContextHolder.clearContext();
httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, authenticationException.getMessage());
}
}
private void processTokenAuthentication(String apiKey) {
SessionCredentials authCredentials = new SessionCredentials(apiKey);
Authentication requestAuthentication = new PreAuthenticatedAuthenticationToken(authCredentials, authCredentials);
Authentication resultOfAuthentication = tryToAuthenticate(requestAuthentication);
SecurityContextHolder.getContext().setAuthentication(resultOfAuthentication);
}
private Authentication tryToAuthenticate(Authentication requestAuthentication) {
Authentication responseAuthentication = authenticationManager.authenticate(requestAuthentication);
if (responseAuthentication == null || !responseAuthentication.isAuthenticated()) {
throw new InternalAuthenticationServiceException("Unable to authenticate Domain User for provided credentials");
}
return responseAuthentication;
}
}
public class TokenAuthenticationProvider implements AuthenticationProvider {
private String apiKey;
public TokenAuthenticationProvider(String apiKey) {
this.apiKey = apiKey;
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
SessionCredentials credentials = (SessionCredentials) authentication.getCredentials();
if (credentials != null && credentials.apiKey.equals(this.apiKey)) {
//Also evaluate the token here
Authentication newAuthentication = new PreAuthenticatedAuthenticationToken(apiKey, credentials);
newAuthentication.setAuthenticated(true);
return newAuthentication;
}
throw new BadCredentialsException("Bad credentials given.");
}
#Override
public boolean supports(Class<?> aClass) {
return aClass.equals(PreAuthenticatedAuthenticationToken.class);
}
}
Create Session Credentials Holder
public class SessionCredentials {
String apiKey;
String accessToken;
public SessionCredentials(String apiKey, String accessToken) {
this.apiKey = apiKey;
this.accessToken = accessToken;
}
public String getApiKey() {
return apiKey;
}
public String getAccessToken() {
return accessToken;
}
}
Finally Register These in your Security Config
//Leave whatever you had here
#Override
public void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(new TokenAuthenticationFilter(authenticationManager()), BasicAuthenticationFilter.class);
String contentPathDir = String.format("/%s/**", contentPath);
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests()
.antMatchers("/authorization/**", "/public/**", "/management/**", "/health/**", contentPathDir).permitAll()
.antMatchers("/**").authenticated();
}
//Add these two below.
#Override
public void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(apiKeyAuthenticationProvider());
}
#Bean
public TokenAuthenticationProvider apiKeyAuthenticationProvider() {
return new TokenAuthenticationProvider(apiKey);
}

Why does this token based Spring Security filter not get called?

I am currently working on a project in which I use Angular JS and Spring REST services. These last few days I've been trying to get some security into the system (see my previous post). I'm implementing token based security.
I got the basic stuff working, but the XAuthTokenFilter doesn't get called when requests are being done. I have no idea why, I think it's something very simple that I'm overlooking. The relevant classes:
XAuthTokenFilter (doFilter does not get called each request)
public class XAuthTokenFilter extends GenericFilterBean {
private final static String XAUTH_TOKEN_HEADER_NAME = "x-auth-token";
private UserDetailsService detailsService;
private TokenProvider tokenProvider;
public XAuthTokenFilter(UserDetailsService detailsService, TokenProvider tokenProvider) {
this.detailsService = detailsService;
this.tokenProvider = tokenProvider;
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
try {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String authToken = httpServletRequest.getHeader(XAUTH_TOKEN_HEADER_NAME);
if (StringUtils.hasText(authToken)) {
String username = this.tokenProvider.getUserNameFromToken(authToken);
UserDetails details = this.detailsService.loadUserByUsername(username);
if (this.tokenProvider.validateToken(authToken, details)) {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(details, details.getPassword(), details.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(token);
}
}
filterChain.doFilter(servletRequest, servletResponse);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}
XAuthTokenConfigurer
public class XAuthTokenConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private TokenProvider tokenProvider;
private UserDetailsService detailsService;
public XAuthTokenConfigurer(UserDetailsService detailsService, TokenProvider tokenProvider) {
this.detailsService = detailsService;
this.tokenProvider = tokenProvider;
}
#Override
public void configure(HttpSecurity http) throws Exception {
XAuthTokenFilter customFilter = new XAuthTokenFilter(detailsService, tokenProvider);
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
}
}
SecurityConfiguration
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Inject
private Http401UnauthorizedEntryPoint authenticationEntryPoint;
#Inject
private UserDetailsService userDetailsService;
#Inject
private TokenProvider tokenProvider;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Inject
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers("/scripts/**/*.{js,html}");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.csrf()
.disable()
.headers()
.frameOptions()
.disable()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/protected/**").authenticated()
.antMatchers("/api/open/**").permitAll()
.and()
.apply(securityConfigurerAdapter());
}
private XAuthTokenConfigurer securityConfigurerAdapter() {
return new XAuthTokenConfigurer(userDetailsService, tokenProvider);
}
/**
* This allows SpEL support in Spring Data JPA #Query definitions.
*
* See https://spring.io/blog/2014/07/15/spel-support-in-spring-data-jpa-query-definitions
*/
#Bean
EvaluationContextExtension securityExtension() {
return new EvaluationContextExtensionSupport() {
#Override
public String getExtensionId() {
return "security";
}
#Override
public SecurityExpressionRoot getRootObject() {
return new SecurityExpressionRoot(SecurityContextHolder.getContext().getAuthentication()) {};
}
};
}
}
I really have no clue why it doesn't get called, is there something wrong with my url antMatcher() statements?
My Context which might be good to include:
#Configuration
#EnableWebMvc
#Import(AppContext.class) // The context from my backend which is included as a dependency
#ComponentScan("com.example.springsecuritytest")
public class RestContext extends WebMvcConfigurerAdapter {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
}
WebAppInitializer
public class WebAppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
//RootContext
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(RestContext.class);
// Add RootContext using ContextLoaderListener
servletContext.addListener(new ContextLoaderListener(rootContext));
// Registering and mapping dispatcher servlet
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", new DispatcherServlet(rootContext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
}
}
Please check out this git repo. I have prepared a very basic setup that test security with and without a tokenAuthenticationFilter. The filter implementation is just a mock, setting a valid Authentication whenever the header is present, regardless of its value. Please also note the two application WebApplicationInitializers, which are Servlet 3.0 conformant and are configuring the Servlet container programmatically (instead of web.xml).

Categories