Spring Security: AuthenticationProcessingFilter is called twice - java

I try configure Spring Security via token authorization in RESTful application.
My AuthenticationFilter looks like:
#Configurable
public class CustomTokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private static final Logger logger = LoggerFactory.getLogger(CustomTokenAuthenticationFilter.class);
private final static String SECRET_KEY = "ThisIsASecretKey";
public final String HEADER_SECURITY_TOKEN = "X-Token";
#Inject
private Users usres;
public CustomTokenAuthenticationFilter(String defaultFilterProcessesUrl) {
super(defaultFilterProcessesUrl);
super.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher(defaultFilterProcessesUrl));
setAuthenticationManager(new NoOpAuthenticationManager());
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws IOException,
ServletException {
String token = request.getHeader(HEADER_SECURITY_TOKEN);
logger.info("token found:" + token);
TokenInfo tokenInfo = new TokenInfo(token, SECRET_KEY);
AbstractAuthenticationToken userAuthenticationToken;
try {
userAuthenticationToken = authUserByToken(tokenInfo);
if (userAuthenticationToken == null)
throw new AuthenticationServiceException(MessageFormat.format("Error | {0}", "Bad Token"));
return userAuthenticationToken;
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
private AbstractAuthenticationToken authUserByToken(TokenInfo token) throws ParseException {
if (token == null) {
return null;
}
UserInfo userInfo = usres.findUser(token.getUsername());
ModelMapper mapper = new ModelMapper();
mapper.getConfiguration().setProvider(new UserProvider());
User userDetails = mapper.map(userInfo, User.class);
AbstractAuthenticationToken authToken = new AuthenticationToken(userDetails);
try {
return authToken;
} catch (Exception e) {
logger.error("Authenticate user by token error: ", e);
}
return authToken;
}
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
setAuthenticationSuccessHandler(new SimpleUrlAuthenticationSuccessHandler() {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
chain.doFilter(request, response);
}
});
super.doFilter(req, res, chain);
}
}
and Spring Security config:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Inject
AuthenticationManager authenticationManager;
#Bean
protected AbstractAuthenticationProcessingFilter getTokenAuthFilter() throws Exception {
CustomTokenAuthenticationFilter tapf = new CustomTokenAuthenticationFilter("/api/secure-module/admin/**");
tapf.setAuthenticationManager(authenticationManager);
return tapf;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.csrf().disable().addFilterBefore(getTokenAuthFilter(), AnonymousAuthenticationFilter.class).exceptionHandling()
.authenticationEntryPoint(new RestAuthenticationEntryPoint());
}
}
It works fine but CustomTokenAuthenticationFilter is called twice and I don't know why. Any ideas?

I found problem, it is #Bean annotation in getTokenAuthFilter method. Then I had 2 registered filter in chain (additionalFilters, originalChain).

I had a similar experience when the Filter was generating an exception causing a redirect to /error which triggered the Filter again. I had to specify
#Override
public void configure(WebSecurity web) throws Exception {
// ignoring security for /error
web.ignoring().antMatchers("/error");
}

Related

Spring Boot Custom error object for InsufficientAuthenticationException

I have implemented authentication for my APIs and it works as expected . The user first access the auth api to get a token by passing username and password. This api returns a token. The user then calls the secure apis by passing the tokens.
This issue is when the user passes an invalid token or does not pass a token the default error object is returned from Spring Boot. I wanna customize this object and for this, I wrote a custom exception handler extending ResponseEntityExceptionHandler but this is not getting triggered as the exception is thrown before the controller kicks in.
#ExceptionHandler(value = {InsufficientAuthenticationException.class})
public final ResponseEntity<Object>
authenticationException(InsufficientAuthenticationException ex) {
List<String> details = new ArrayList<>();
details.add("Authentication is required to access this resource");
ErrorResponse error = new ErrorResponse("error", "Unauthorized", details);
return new ResponseEntity(error, HttpStatus.FORBIDDEN);
}
The AuthenticationProvider is responsible to find user based on the authentication token sent by the client in the header. This is how our Spring based token authentication provider looks like:
#Component
public class AuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
#Autowired
CustomerService customerService;
#Override
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) throws AuthenticationException {
//
}
#Override
protected UserDetails retrieveUser(String userName, UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) throws AuthenticationException {
Object token = usernamePasswordAuthenticationToken.getCredentials();
return Optional
.ofNullable(token)
.map(String::valueOf)
.flatMap(customerService::findByToken)
.orElseThrow(() -> new UsernameNotFoundException("Cannot find user with authentication token=" + token));
}
The token authentication filter is responsible to get the authentication filter from the header and call the authentication manager for authentication. This is how the authentication filter looks like:
public class AuthenticationFilter extends AbstractAuthenticationProcessingFilter {
AuthenticationFilter(final RequestMatcher requiresAuth) {
super(requiresAuth);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException {
Optional tokenParam = Optional.ofNullable(httpServletRequest.getHeader(AUTHORIZATION)); //Authorization: Bearer TOKEN
String token= httpServletRequest.getHeader(AUTHORIZATION);
token= StringUtils.removeStart(token, "Bearer").trim();
Authentication requestAuthentication = new UsernamePasswordAuthenticationToken(token, token);
return getAuthenticationManager().authenticate(requestAuthentication);
}
#Override
protected void successfulAuthentication(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain, final Authentication authResult) throws IOException, ServletException {
SecurityContextHolder.getContext().setAuthentication(authResult);
chain.doFilter(request, response);
}
}
Spring security configuration looks like:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private static final RequestMatcher PROTECTED_URLS = new OrRequestMatcher(
new AntPathRequestMatcher("/api/**")
);
AuthenticationProvider provider;
public SecurityConfiguration(final AuthenticationProvider authenticationProvider) {
super();
this.provider = authenticationProvider;
}
#Override
protected void configure(final AuthenticationManagerBuilder auth) {
auth.authenticationProvider(provider);
}
#Override
public void configure(final WebSecurity webSecurity) {
webSecurity.ignoring().antMatchers("/token/**");
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling()
.and()
.authenticationProvider(provider)
.addFilterBefore(authenticationFilter(), AnonymousAuthenticationFilter.class)
.authorizeRequests()
.requestMatchers(PROTECTED_URLS)
.authenticated()
.and()
.csrf().disable()
.formLogin().disable()
.httpBasic().disable()
.logout().disable();
http
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint());
}
#Bean
AuthenticationFilter authenticationFilter() throws Exception {
final AuthenticationFilter filter = new AuthenticationFilter(PROTECTED_URLS);
filter.setAuthenticationManager(authenticationManager());
//filter.setAuthenticationSuccessHandler(successHandler());
return filter;
}
#Bean
AuthenticationEntryPoint forbiddenEntryPoint() {
return new HttpStatusEntryPoint(HttpStatus.FORBIDDEN);
}
#Autowired
private HandlerExceptionResolver handlerExceptionResolver;
public AuthenticationEntryPoint authenticationEntryPoint() {
log.error("in authenticationEntryPoint");
return new AuthenticationEntryPoint() {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
log.error("in commence");
try {
log.error(authException.getLocalizedMessage());
handlerExceptionResolver.resolveException(request, response, null, authException);
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new ServletException(e);
}
}
};
}
}
P.S.: Refer to https://www.javadevjournal.com/spring/securing-a-restful-web-service-with-spring-security/
Since you are customising AbstractAuthenticationProcessingFilter , you can also customise its AuthenticationFailureHandler which will be invoked when attemptAuthentication() throw AuthenticationException. You can then handle the error at there.
An example is :
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler{
#Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response, AuthenticationException exception)
throws IOException, ServletException {
//create your custom error object
CustomError error = xxxxx;
response.setStatus(HttpStatus.FORBIDDEN.value());
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
// Format the custom error object as JSON string , for example using Jackson :
ObjectMapper mapper = new ObjectMapper();
response.getWriter().write(mapper.writeValueAsString(error));
}
}
And configure to use it:
#Bean
AuthenticationFilter authenticationFilter() throws Exception {
final AuthenticationFilter filter = new AuthenticationFilter(PROTECTED_URLS);
filter.setAuthenticationFailureHandler(new MyAuthenticationFailureHandler());
return filter;
}

Why BadCredentialsException can not be thrown or handled in my customized AuthenticationProvider?

I try to use JWT to secure my resource based on Spring Security to achieve the following:
1. Token invalid or expired, return 401.
2. Successfully authorized but have no right to reach some controllers. Then return 403.
Now there is something wrong with it. I throw BadCredentialsException in my customized AuthenticationProvider (named TokenAuthenticationProvider) while user fails to be authenticated. But it finally returns 403. What can I do to handle the exception and return 403 http code.
I tried to implement AuthenticationEntryPoint but it doesn't work.
And one another way to handle the exception is using customized filter to catch the Exception. But this way definitely doesn't work because even the http response doesn't show 500 BadCredentialsException. So there must be a place already catching this Exception and I can't understand.
TokenAuthenticationProvider.class
public class TokenAuthenticationProvider implements AuthenticationProvider {
UserService userService;
public TokenAuthenticationProvider(UserService userService) {
this.userService = userService;
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
throw new BadCredentialsException("hello");
}
#Override
public boolean supports(Class<?> aClass) {
System.out.println(aClass);
TokenAuthenticationProvider.class.isAssignableFrom(aClass);
return true;
}
}
WebSecurity.class
#EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
#Autowired
UserService userService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/**")
.addFilterAfter(new TokenAuthenticationFilter(), BasicAuthenticationFilter.class)
.authorizeRequests()
.anyRequest().hasRole("API");
}
#Override
protected void configure(AuthenticationManagerBuilder auth){
auth.authenticationProvider(new TokenAuthenticationProvider(userService));
}
}
TokenAuthenticationFilter.class
public class TokenAuthenticationFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
SecurityContextHolder.getContext().setAuthentication(new TokenAuthentication("hello"));
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
The above code has been simplified. Instead of following a normal process, I directly throw the BadCredentialsException. What can I do to handle this Exception and return 401 http code.
You need to implement two filters to control the JWT generated.
First Filter is to authenticate and send the JWT to the client when the authentication is successful.
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
public JWTAuthenticationFilter(AuthenticationManager authenticationManger) {
this.authenticationManager = authenticationManger;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
try {
AuthenticationRequest authRequest = new ObjectMapper().readValue(request.getInputStream(),
AuthenticationRequest.class);
return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
authRequest.getUsername(), authRequest.getPassword(), new ArrayList<>()));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
FilterChain chain, Authentication auth) throws IOException {
Date expirationDate = DateUtil.getDateAddDays(new Date(), 1);
String token = Jwts.builder().setIssuedAt(new Date()).setIssuer(WebSecurity.ISSUER)
.setSubject(((ClientDetails)auth.getPrincipal()).getUsername())
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS512, HardCodeUtil.JWT_KEY).compact();
response.addHeader(WebSecurity.HEADER_AUTHORIZATION, WebSecurity.PREFIX_JWT + token);
response.addHeader(WebSecurity.HEADER_JWT_EXPIRATION_DATE, String.valueOf(expirationDate.getTime()));
ObjectMapper mapper = new ObjectMapper();
ClientExtraParams extraParams = new ClientExtraParams((byte)1);
String body = mapper.writeValueAsString(new ClientLoginResponse(((ClientDetails)auth.getPrincipal()).getClient(),
extraParams));
response.setContentType("application/json");
response.getWriter().write(body);
response.getWriter().flush();
response.getWriter().close();
}
}
The second Filter is to validate every JWT before access to the resources:
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
private static final Logger log = Logger.getLogger(JWTAuthorizationFilter.class.getName());
public JWTAuthorizationFilter(AuthenticationManager authManager) {
super(authManager);
}
#Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
throws IOException, ServletException {
String header = req.getHeader(WebSecurity.HEADER_AUTHORIZATION);
if (header == null || !header.startsWith(WebSecurity.PREFIX_JWT)) {
chain.doFilter(req, res);
return;
}
try {
UsernamePasswordAuthenticationToken authentication = getAuthentication(req);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(req, res);
}catch (SignatureException ex) {
log.log(Level.SEVERE, "JWT SIGNING INVALID");
}catch (MalformedJwtException ex) {
log.log(Level.SEVERE, "JWT STRUCTURE INVALID");
}catch (ExpiredJwtException ex) {
log.log(Level.SEVERE, "JWT EXPIRED");
GeneralResponse jwtInvalidResponse = new GeneralResponse(ErrorsEnum.JWT_EXPIRED);
ObjectMapper mapper = new ObjectMapper();
String body = mapper.writeValueAsString(jwtInvalidResponse);
res.setContentType("application/json");
res.getWriter().write(body);
res.getWriter().flush();
res.getWriter().close();
}catch (UnsupportedJwtException ex) {
log.log(Level.SEVERE, "JWT UNSUPPORTED");
}catch (IllegalArgumentException ex) {
log.log(Level.SEVERE, "ILLEGAL ARGUMENT JWT ENVIADO");
}
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
String token = request.getHeader(WebSecurity.HEADER_AUTHORIZATION);
if (token != null) {
String user = Jwts.parser()
.setSigningKey(HardCodeUtil.JWT_KEY)
.parseClaimsJws(token.replace(WebSecurity.PREFIX_JWT, ""))
.getBody()
.getSubject();
if (user != null) {
return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
}
}
return null;
}
}
In your Spring configuration of HttpSecurity add these filters:
.and().addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()));
I implemented this using this library:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>

Catching custom exception thrown from subclass of OncePerRequestFilter in AuthenticationEntryPoint

I have a JWTAuthFilter that extends OncePerRequestFilter where I am validating the token.
The validateToken method throws custom exceptions(CredentialsChangedException and TooManyDevicesException which extend org.springframework.security.core.AuthenticationException)
These exceptions are caught in the filter properly but when they move forward to the AuthenticationEntryPoint, the AuthenticationException turns into an instanceof InsufficientAuthenticationException and the custom error message that I want to return as a response is lost.
#Component
public class JwtAuthFilter extends OncePerRequestFilter {
private static final String BEARER = "Bearer ";
private static final Logger logger = LoggerFactory.getLogger(JwtAuthFilter.class);
#Autowired
private UserDetailsService jwtUserDetailsService;
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Value ("${jwt.http.request.header}")
private String tokenHeader;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String requestTokenHeader = request.getHeader(this.tokenHeader);
String username = null;
String jwtToken = null;
if((requestTokenHeader != null) && requestTokenHeader.startsWith(BEARER)) {
jwtToken = requestTokenHeader.substring(7);
try {
username = jwtTokenUtil.getUsernameFromToken(jwtToken);
if((username != null) && (SecurityContextHolder.getContext()
.getAuthentication() == null)) {
UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username);
if(jwtTokenUtil.validateToken(jwtToken, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(userDetails, null,
userDetails.getAuthorities());
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext()
.setAuthentication(usernamePasswordAuthenticationToken);
}
}
}
catch (IllegalArgumentException e) {
logger.error("Unable to get username from JWT. ", e.getLocalizedMessage());
}
catch (ExpiredJwtException e) {
logger.warn("Expired JWT. ", e.getLocalizedMessage());
}
catch (Exception e) {
logger.error(e.getLocalizedMessage());
}
}
chain.doFilter(request, response);
}
}
#Component
public class JwtUnAuthorizedResponseAuthenticationEntryPoint implements AuthenticationEntryPoint , Serializable {
private static final long serialVersionUID = - 8970718410437077606L;
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)
throws IOException {
if(e instanceof TooManyDevicesException) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, Constants.TOO_MANY_DEVICES);
}
else if(e instanceof CredentialsChangedException) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, Constants.CREDENTIALS_CHANGED);
}
else {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, Constants.JWT_EXPIRED);
}
}
}
I want to send an appropriate unauthorized response from my filter, is there a way to do this?
I think once you caught the AuthenticationException in JwtAuthFilter, you should not move forward to the next filter as most probably an AnonymousAuthenticationFilter will sit in the later part of the filter chain and this filter will configure the current request to be an anonymous user if SecurityContextHolder is empty (i.e happen when authentication fail). The InsufficientAuthenticationException is most probably due to Spring considers the current request is an anonymous user who access some protected URL or methods.
Instead , once you catch AuthenticationException in your JwtAuthFilter , you should then call AuthenticationEntryPoint.commence() and end the filter chain . This is also how the BasicAuthenticationFilter is doing now .
So , I suggest revise the JwtAuthFilter to :
#Component
public class JwtAuthFilter extends OncePerRequestFilter {
#Autowired
private AuthenticationEntryPoint authenticationEntryPoint;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
try{
//validate JWT
} catch (AuthenticationException e) {
logger.error(e.getLocalizedMessage());
SecurityContextHolder.clearContext();
this.authenticationEntryPoint.commence(request, response, e);
return;
}
}
}
As a workaround for now I simply added my custom error from the JwtAuthFilter as a request attribute and retrieved it in the AuthenticationEntryPoint

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();
}
}

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);
}

Categories