Access Deny and Allow Functionality using Spring and Spring security - spring

Currently I am trying to implement authentication example using spring MVC and spring boot with spring security. In my sample application what I am trying to do is - I am sending one authentication token in header of one URL. I need to take this authentication token from URL and decode. If username and password is matching , then only need to transfer the control to end point "api/getStudent/v1" or something like this. Otherwise from there only need to give the response that denying.
For this Currently I tried with authentication provider from spring security. But it is not suitable for taking the token from header of request. Here my confusion is that , from spring security which method I have to implement here ? Can anyone suggest a standard way of implementation ? Or Any documentation for this type of implementation?

All you need to do is create a custom security filter and plug this filter before spring security BasicAuthenticationFilter. Sample code -
public class CustomAuthenticationFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain) throws ServletException, IOException {
String authHeader = request.getHeaders("Authorization");
//Decode the authHeader
//Validate the authHeader with your username & password
if(invalid) {
//throw exception and abort processing
}
filterChain.doFilter(request, response);
}
}
Now either you can create the bean OR make this as #component so that spring picks it up and creates bean for you.
In your security configuration, add following -
#Configuration
public class CustomWebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterAfter(new CustomAuthenticationFilter(), BasicAuthenticationFilter.class);
}
}

You can try out the following. I have used JWT authentication here. And as per your problem you can preauthorize your end point "api/getStudent/v1" with spring's #Preauthorize annotation.
Following is the end point where user will be directed upon the signin.
#PostMapping("/signin")
public ResponseEntity<?> authenticateUser(#Valid #RequestBody LoginForm loginRequest) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginRequest.getEmail(), loginRequest.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = jwtProvider.generateJwtToken(authentication);
UserPrinciple userPrinciple = (UserPrinciple) authentication.getPrincipal();
String name = userRepo.findById(userPrinciple.getId()).get().getName();
return ResponseEntity.ok(new JwtResponse(jwt, userPrinciple.getUsername(),
userPrinciple.getAuthorities(),name,userPrinciple.getGender()));
}
Following is the WebSecurityConfig class
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(
prePostEnabled = true
)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailsServiceImpl userDetailsService;
#Autowired
private JwtAuthEntryPoint unauthorizedHandler;
#Bean
public JwtAuthTokenFilter authenticationJwtTokenFilter() {
return new JwtAuthTokenFilter();
}
#Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public AuthorizationRequestRepository<OAuth2AuthorizationRequest> customAuthorizationRequestRepository() {
return new HttpSessionOAuth2AuthorizationRequestRepository();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and()
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler)
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
Following JWTProvider class includes the method to generate the JWT token.(note: I have set the email of each user as the username. You can do it according to your wish)
#Component
public class JwtProvider {
#Autowired
UserRepository userRepo;
private static final Logger logger = LoggerFactory.getLogger(JwtProvider.class);
public String generateJwtToken(Authentication authentication) {
UserPrinciple userPrincipal = (UserPrinciple) authentication.getPrincipal();
String name = userRepo.findById(userPrincipal.getId()).get().getName();
return Jwts.builder()
.setSubject((userPrincipal.getUsername())) //getUsername returns the email
.claim("id",userPrincipal.getId() )
.claim("name",name)
.setIssuedAt(new Date())
.setExpiration(new Date((new Date()).getTime() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
}
public String generateJwtToken(UserPrinciple userPrincipal) {
String name = userRepo.findById(userPrincipal.getId()).get().getName();
return Jwts.builder()
.setSubject((userPrincipal.getUsername())) //getUsername returns the email
.claim("id",userPrincipal.getId() )
.claim("name",name)
.setIssuedAt(new Date())
.setExpiration(new Date((new Date()).getTime() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
}
public boolean validateJwtToken(String authToken) {
try {
Jwts.parser().setSigningKey(SECRET).parseClaimsJws(authToken);
return true;
} catch (SignatureException e) {
logger.error("Invalid JWT signature -> Message: {} ", e);
} catch (MalformedJwtException e) {
logger.error("Invalid JWT token -> Message: {}", e);
} catch (ExpiredJwtException e) {
logger.error("Expired JWT token -> Message: {}", e);
} catch (UnsupportedJwtException e) {
logger.error("Unsupported JWT token -> Message: {}", e);
} catch (IllegalArgumentException e) {
logger.error("JWT claims string is empty -> Message: {}", e);
}
return false;
}
public String getUserNameFromJwtToken(String token) {
return Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token)
.getBody().getSubject();
}
}
Following is the JWTAuthTokenFilter class which is initiated in WebSecurityConfig class. Here is where it decodes the token from the rquest and checks whether the token is valid or not
public class JwtAuthTokenFilter extends OncePerRequestFilter {
#Autowired
private JwtProvider tokenProvider;
#Autowired
private UserDetailsServiceImpl userDetailsService;
private static final Logger logger = LoggerFactory.getLogger(JwtAuthTokenFilter.class);
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
String jwt = getJwt(request);
if (jwt != null && tokenProvider.validateJwtToken(jwt)) {
String email = tokenProvider.getUserNameFromJwtToken(jwt);//returns the email instead of username
UserDetails userDetails = userDetailsService.loadUserByUsername(email);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception e) {
logger.error("Can NOT set user authentication -> Message: {}", e);
}
filterChain.doFilter(request, response);
}
private String getJwt(HttpServletRequest request) {
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
return authHeader.replace("Bearer ", "");
}
return null;
}
}
Following is the JWTAuthEntryPoint . Check WebSecurityConfig class for the use of this class
#Component
public class JwtAuthEntryPoint implements AuthenticationEntryPoint {
private static final Logger logger = LoggerFactory.getLogger(JwtAuthEntryPoint.class);
#Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException e)
throws IOException, ServletException {
logger.error("Unauthorized error. Message - {}", e.getMessage());
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Error -> Unauthorized");
}
}
Following is the class I created for the constraints
public class SecurityConstraints {
public static final String SECRET = "********";//add any secret you want
public static final long EXPIRATION_TIME = 864_000_000L;
}

Seem like you are working with REST API, you can use JWT and Custom Filter similar to this (https://medium.com/#hantsy/protect-rest-apis-with-spring-security-and-jwt-5fbc90305cc5)

I am sending one authentication token in header of one URL. I need to
take this authentication token from URL and decode. If username and
password is matching...
Usually, the goal of using tokens for authentication is to get rid of username and password check.
Basic HTTP authentication that is supported by Spring Security out of the box assumes passing base64 encoded username and password in the HTTP header: e.g. Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l (base64 encoded Aladdin:OpenSesame).
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/public").permitAll()
.anyRequest().authenticated()
.and()
.httpBasic();
}
}
If you still need to extract username and password from a token in a different way, consider the following example.
Considering you have the following REST controller:
#RestController
public class TestRestController {
#GetMapping("/api/getStudent/v1")
public String helloWorld() {
return "Hello, World!";
}
#GetMapping("/info")
public String test() {
return "Test";
}
}
In order to make endpoint /api/getStudent/v1 protected and /info public, and extract principal and credentials from the HTTP request header you need to implement custom AbstractAuthenticationProcessingFilter:
public class HeaderUsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public HeaderUsernamePasswordAuthenticationFilter(RequestMatcher requiresAuthenticationRequestMatcher) {
super(requiresAuthenticationRequestMatcher);
setAuthenticationSuccessHandler((request, response, authentication) -> {
});
setAuthenticationFailureHandler((request, response, exception) ->
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, exception.getMessage()));
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
String token = request.getHeader("token");
String username = token; //get username from token
String password = token; //get password from token
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(username, password);
return getAuthenticationManager().authenticate(authenticationToken);
}
#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);
}
}
This filter must extract principal and credentials from the token passed in header and attempt an authentication with Spring Security.
Next, you have to create an instance of this custom filter and configure Spring Security to add the filter in the security filter chain (.addFilterBefore(authenticationFilter(), UsernamePasswordAuthenticationFilter.class)):
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public HeaderUsernamePasswordAuthenticationFilter authenticationFilter() throws Exception {
HeaderUsernamePasswordAuthenticationFilter authenticationFilter =
new HeaderUsernamePasswordAuthenticationFilter(new AntPathRequestMatcher("/api/**"));
authenticationFilter.setAuthenticationManager(authenticationManagerBean());
return authenticationFilter;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.addFilterBefore(
authenticationFilter(),
UsernamePasswordAuthenticationFilter.class);
}
//...
}
It is important to make the filter aware of the Spring Security authenticationManagerBean: authenticationFilter.setAuthenticationManager(authenticationManagerBean());.
You can configure what endpoints to protect with aunthentication by passing a RequestMatcher: e.g. new AntPathRequestMatcher("/api/**").
For testing, you can create in-memory UserDetailsService and test user with username test, password test and authority admin:
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//...
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("test")
.password(passwordEncoder().encode("test"))
.authorities("admin");
}
}
Run the application and try to access the public endpoint without an authentication:
curl -i http://localhost:8080/info
HTTP/1.1 200
Test
the protected endpoint without an authentication:
curl -i http://localhost:8080/api/getStudent/v1
HTTP/1.1 401
the protected endpoint without an invalid token:
curl -i http://localhost:8080/api/getStudent/v1 -H 'token: not_valid'
HTTP/1.1 401
and finally the protected endpoint with a valid token:
curl -i http://localhost:8080/api/getStudent/v1 -H 'token: test'
HTTP/1.1 200
Hello, World!

Related

Spring Security multiple configuration

I try to make spring boot multiple configuration. There are next configurations. First filter, I use for verify client. It should be in every request. I wanna try make extra rule for verify user permission. It's mean, when someone wants to save something, he/she have to be authorized user and send personal token in header. If that token is valid, I can allow to save file. But JWT token must to be too. Finally, for save I would like to use two tokens. First is JWT and second is user token.
This part of code verify permission for access to API. It should be in header in each request. Now, it works.
#Configuration
#EnableWebSecurity
public class SecurityConfiguration {
#Order(1)
#Configuration
public static class JwtTokenSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.addFilterAfter(new JWTAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/user/new").hasAuthority("ADMIN")
.anyRequest().authenticated();
}
}
This part of code have to verify user. logged in or no. It doesn't work now. When I try to get access to "/v1/save_file", it check only JWT token, not user-token. Finally, I would like to make to checks for that endpoint. first is verify JWT token, second is verify user-token for save.
#Order(2)
#Configuration
public static class UserTokenSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Value("${fc.security.header.user-token:User-Token}")
private String usrTokenHeaderName;
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
UserTokenSecurityConfig userToken = new UserTokenSecurityConfig(usrTokenHeaderName);
userToken.setAuthenticationManager(new AuthenticationManager() {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String principal = (String) authentication.getPrincipal();
if (!usrTokenHeaderName.equals(principal)) {
throw new BadCredentialsException("The Application token was not found or not the expected value.");
}
System.out.println(principal + " " + usrTokenHeaderName);
authentication.setAuthenticated(true);
return authentication;
}
});
httpSecurity.antMatcher("/v1/save_file")
.csrf()
.disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilter(userToken)
.addFilterBefore(new ExceptionTranslationFilter(new Http403ForbiddenEntryPoint()), userToken.getClass())
.authorizeRequests()
.anyRequest()
.authenticated();
}
}
}
JWTAuthorizationFilter class
public class JWTAuthorizationFilter extends OncePerRequestFilter {
private final String HEADER = "Authorization";
private final String PREFIX = "Bearer ";
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
try {
if (checkJWTToken(request, response)) {
Claims claims = validateToken(request);
if (claims.get("authorities") != null) {
setUpSpringAuthentication(claims);
} else {
SecurityContextHolder.clearContext();
}
} else {
SecurityContextHolder.clearContext();
}
chain.doFilter(request, response);
} catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException e) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
return;
}
}
private Claims validateToken(HttpServletRequest request) {
String jwtToken = request.getHeader(HEADER).replace(PREFIX, "");
return Jwts.parser().setSigningKey(Constants.SECRET_KEY.getBytes()).parseClaimsJws(jwtToken).getBody();
}
/**
* Authentication method in Spring flow
*
* #param claims
*/
private void setUpSpringAuthentication(Claims claims) {
#SuppressWarnings("unchecked")
List<String> authorities = (List<String>) claims.get("authorities");
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(claims.getSubject(), null,
authorities.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()));
SecurityContextHolder.getContext().setAuthentication(auth);
}
private boolean checkJWTToken(HttpServletRequest request, HttpServletResponse res) {
String authenticationHeader = request.getHeader(HEADER);
if (authenticationHeader == null || !authenticationHeader.startsWith(PREFIX))
return false;
return true;
}
}
very simple UserTokenSecurityConfig class
public class UserTokenSecurityConfig extends AbstractPreAuthenticatedProcessingFilter {
private String userHeader;
public UserTokenSecurityConfig(String userHeader) {
this.userHeader = userHeader;
}
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest httpServletRequest) {
return httpServletRequest.getHeader(userHeader);
}
#Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest httpServletRequest) {
return "NA";
}
}

URL access denying when implementing the Spring Security for URL authentication

I am trying to implement URL authentication before it giving the response through business logic. For that I am using the authentication provider from Spring Security and trying to do one simple demo for testing authenticationProvider working properly. After this I am going to modify by adding my business logic.
My security config file SecurityConfig.java like the following,
#EnableWebSecurity
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthenticationProvider authenticationProvider;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.anyRequest()
.authenticated()
.and().httpBasic();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
auth.authenticationProvider(authenticationProvider);
}
}
And My CustomAuthenticationProvider.java implementation like the following,
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider
{
#SuppressWarnings("unused")
#Override
public Authentication authenticate(Authentication authToken) throws AuthenticationException {
String userToken = (String) authToken.getName();
String responseString = "test";
String password = "test";
if(responseString.equals(userToken)) {
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(userToken, password);
return auth;
}
else {
return null;
}
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
And my TestSecurity.java like the following,
#RestController
public class TestSecurity {
#GetMapping("/security/load")
public String LoadSecureUsers() {
return "hello spring security";
}
}
When I am calling the URL localhost:8585/security/load with headers authToken: "test" from POSTMAN application, I am getting the following,
{
"timestamp": "2019-10-30T07:24:25.165+0000",
"status": 401,
"error": "Unauthorized",
"message": "Unauthorized",
"path": "/security/load"
}
If the condition are satisfying in IF, then how the URL is not able to access? Did I make any mistake in authentication Provider implementation?
Instead of AuthenticationProvider use filter to process the request. This code might help you:
public class ApplicationAuthFilter extends BasicAuthenticationFilter {
public ApplicationAuthFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
UsernamePasswordAuthenticationToken authentication = getAuthentication(request);
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
String token = String bearerToken = req.getHeader("accessToken");
String username = "test";
String password = "test"
if (username != null && !username.isEmpty()) {
return new UsernamePasswordAuthenticationToken(username, null, authorities);
}
return null;
}
}
And your security config file like this:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilter(new ApplicationAuthFilter(authenticationManager()))
}
}
Basically you need to read the header information which you are passing with request and based on that you have to take action.
Hope this helps.

How does Spring Security Filter Work With Custom Authentication and How To Combine It with Servlet Filter?

So I have a question regarding Spring Security. So I want to check authentication using custom header which then I want to check the token given in the custom header to redis value and set the data object as credentials at custom implementation of abstract authentication token.
I have already followed the tutorial in this web: https://shout.setfive.com/2015/11/02/spring-boot-authentication-with-custom-http-header/, but I can't update the authentication interface in SecurityContextHolder.getContext() (I set the credentials in my implementation of Authentication Interface, but when I get it in the service, the credentials is null).
I also found other problems, I actually want to order the filter like this:
ExceptionHandlerFilter (to catch exception error in the filter) -> Other filter or CustomWebSecurityConfigurerAdapter.
But when the url matches the antMatcher, I found that ExceptionHandlerFilter was skipped by the application.
I was so confused by this and could not find better tutorial in implementing custom authentication using Spring Security. So I want to ask whether you guys can tell me how Spring Security works and how to combine it with Filter?
Here is my first filter to catch exception
#Component
#Order(0)
public class ExceptionHandlerFilter extends OncePerRequestFilter {
private JaminExceptionHandler exceptionHandler;
private ObjectMapper objectMapper = new ObjectMapper();
#Autowired
public ExceptionHandlerFilter(JaminExceptionHandler exceptionHandler) {
this.exceptionHandler = exceptionHandler;
}
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
try {
filterChain.doFilter(request, response);
} catch (Throwable exception) {
ResponseEntity<?> responseEntity = this.exceptionHandler.handleException(exception, request);
response.setStatus(responseEntity.getStatusCode().value());
response.setHeader("Content-Type", "application/json");
response.getWriter().write(this.objectMapper.writeValueAsString(responseEntity.getBody()));
}
}
}
Here is my Auth Filter
#Component
public class AuthFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String token = request.getHeader("J-Auth");
if (token != null) {
Authentication auth = new JaminAuthenticationToken(token);
SecurityContextHolder.getContext().setAuthentication(auth);
filterChain.doFilter(request, response);
} else {
throw new JaminException("Not authorized", JaminExceptionType.NOT_AUTHORIZED, HttpStatus.UNAUTHORIZED);
}
}
}
Authentication Provider
#Component
public class JaminAuthenticationProvider implements AuthenticationProvider {
private RedisTemplate<String, String> authRedis;
private ObjectMapper objectMapper = new ObjectMapper();
#Autowired
public JaminAuthenticationProvider(#Qualifier("authRedis") RedisTemplate<String, String> authRedis) {
this.authRedis = authRedis;
}
private UserDTO getUserDTO(String token) throws IOException {
String userData = this.authRedis.opsForValue().get(token);
if (userData == null) {
throw new JaminException("Not authorized", JaminExceptionType.NOT_AUTHORIZED, HttpStatus.UNAUTHORIZED);
}
return this.objectMapper.readValue(userData, UserDTO.class);
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
JaminAuthenticationToken auth = (JaminAuthenticationToken) authentication;
try {
UserDTO userDTO = this.getUserDTO(auth.getToken());
auth.setCredentials(userDTO);
return auth;
} catch (IOException e) {
e.printStackTrace();
}
throw new JaminException("Not authorized", JaminExceptionType.NOT_AUTHORIZED, HttpStatus.UNAUTHORIZED);
}
#Override
public boolean supports(Class<?> authentication) {
return JaminAuthenticationToken.class.isAssignableFrom(authentication);
}
}
WebSecurityConfigurerAdapter
#Configuration
#EnableWebSecurity
#Order(1)
public class JaminSecurityAdapter extends WebSecurityConfigurerAdapter {
#Autowired
private JaminAuthenticationProvider jaminAuthenticationProvider;
private void disableDefaultSecurity(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.csrf().disable();
http.formLogin().disable();
http.logout().disable();
http.httpBasic().disable();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
this.disableDefaultSecurity(http);
http.antMatcher("/auth/check")
.authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilterBefore(new AuthFilter(), BasicAuthenticationFilter.class);
// http.authorizeRequests().anyRequest().permitAll();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(jaminAuthenticationProvider);
}
}
Spring Security has some "before and after" steps. There are a few Handlers that can help. I don't know your code, but if you can get your authentication ok, maybe you just have to extend a SuccessHandler and set the authentication there, like i did in my blog project:
if(checkEmail(authentication)) {
val adminRole = SimpleGrantedAuthority("ROLE_ADMIN")
val oldAuthorities = SecurityContextHolder.getContext().getAuthentication().getAuthorities()
val updateAuthorities = mutableListOf<GrantedAuthority>()
updateAuthorities.add(adminRole)
updateAuthorities.addAll(oldAuthorities)
SecurityContextHolder.getContext().setAuthentication(UsernamePasswordAuthenticationToken(authentication.getPrincipal(),
authentication.getCredentials(),
updateAuthorities))
}
And about the filters, maybe you can find your answer here. I don't like using filters and interceptors, but sometimes they are really necessary.

Spring Boot and Spring Security filter not filtering correct request

I have a spring boot and spring security service.
I have extended WebSecurityConfigurerAdapter class and overridden configure method. But somehow it is not filtering correct request.
My url is something like -
localhost:8080/album/private/v1/getAlbumsByVendorId?vendorId=1
localhost:8080/vendor/private/v1/getVendor?vendorId=1
and also I have some URL which I do not want to authenticate.like below url.
localhost:8080/category/v1/getCategory
Only want to authenticate if the URL contains private.
But seems like my filter is getting invoked for all request.
is there something wrong in .antMatchers("/**/private/**")
Note - I don't have any context path as of now.
Added the classes.
Controller is just a dummy test controller.
#Configuration
#EnableWebSecurity
//#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
#Autowired
private UserDetailsService jwtUserDetailsService;
#Autowired
private JwtRequestFilter jwtRequestFilter;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// configure AuthenticationManager so that it knows from where to load
// user for matching credentials
// Use BCryptPasswordEncoder
auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.cors().disable()
.authorizeRequests()
.antMatchers("/authenticate").permitAll()
.antMatchers("/**/private/**").authenticated()
.anyRequest().authenticated()
.and()
.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class)
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint);
}
}
#Component
public class JWTAuthenticationFilter extends OncePerRequestFilter {
#Autowired
private JwtUserDetailsService jwtUserDetailsService;
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String requestTokenHeader = request.getHeader("Authorization");
String username = null;
String jwtToken = null;
// JWT Token is in the form "Bearer token". Remove Bearer word and get only the Token
if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
jwtToken = requestTokenHeader.substring(7);
try {
username = jwtTokenUtil.getUsernameFromToken(jwtToken);
} catch (IllegalArgumentException e) {
System.out.println("Unable to get JWT Token");
} catch (ExpiredJwtException e) {
System.out.println("JWT Token has expired");
}
} else {
logger.warn("JWT Token does not begin with Bearer String");
}
//Once we get the token validate it.
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username);
// if token is valid configure Spring Security to manually set authentication
if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// After setting the Authentication in the context, we specify
// that the current user is authenticated. So it passes the Spring Security Configurations successfully.
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
#RestController
#CrossOrigin()
public class HelloWorldController {
#RequestMapping({ "/hello" })
public String hello() {
return "Hello World";
}
#RequestMapping({ "/private/test" })
public String hello2() {
return "Hello World-test";
}
#RequestMapping({ "/v1/private/test" })
public String hello3() {
return "Hello World-test-v1";
}
#RequestMapping({ "/v1/public/test" })
public String hello4() {
return "Hello World-test-v1-public";
}
}
By default, Spring Boot will secure all endpoints when Spring Security is on the classpath. We need to explicitly add an exclusion for all other endpoints to be permitted without authentication. Consider change is .anyRequest().permitAll(),
which means each request other than /**/private/** will be accessible to everyone. In other words, the filter will only apply to /**/private/**
Git Link
approach 1 (clean way)
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.cors().disable()
.authorizeRequests()
.antMatchers("/authenticate").permitAll()
.antMatchers("/**/private/**").authenticated()
.anyRequest().permitAll()
.and()
.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class)
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint);
}
approach 2: only check for a token if Request comes from /private/ (not an ideal way)
JwtAuthenticationEntryPoint.java
#Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
System.out.println("Entry Request: "+request.getRequestURI());
System.out.println("Entry Contain: "+request.getRequestURI().contains("private"));
if(request.getRequestURI().contains("private")==true)
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
JwtRequestFilter.java
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String requestTokenHeader = request.getHeader("Authorization");
System.out.println("JWT Request: "+request.getRequestURI());
System.out.println("JWT Contain: "+request.getRequestURI().contains("private"));
String username = null;
String jwtToken = null;
//Remove comment for second approach
if(request.getRequestURI().contains("private")==false)
{
System.out.println("Do Noting, Permit It");
chain.doFilter(request, response);
}
else if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ") ) {
jwtToken = requestTokenHeader.substring(7);
try {
username = jwtTokenUtil.getUsernameFromToken(jwtToken);
} catch (IllegalArgumentException e) {
System.out.println("Unable to get JWT Token");
} catch (ExpiredJwtException e) {
System.out.println("JWT Token has expired");
}
} else {
logger.warn("JWT Token does not begin with Bearer String");
}
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);
}
}
chain.doFilter(request, response);
}
http://localhost:8080/v1/private/test **401**
http://localhost:8080/v1/public/test **200**

Configuring security in a Spring Boot application

I'm upgrading an application to Spring Boot 2.0.3.
But my login request is unauthorized:
curl -H "Accept:application/json" -H "Content-Type: application/json" "http://localhost:8080/api/users/login" -X POST -d "{ \"email\" : \"myemail#somedomain.com\", \"password\" : \"xxxxx\" }" -i
The response is a 401 Unauthorized access. You failed to authenticate.
It is given by my custom entry point:
#Component
public final class RESTAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {
private static Logger logger = LoggerFactory.getLogger(RESTAuthenticationEntryPoint.class);
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authenticationException) throws IOException, ServletException {
logger.debug("Security - RESTAuthenticationEntryPoint - Entry point 401");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized access. You failed to authenticate.");
}
#Override
public void afterPropertiesSet() throws Exception {
setRealmName("User REST");
super.afterPropertiesSet();
}
}
The debugger shows the authenticate method of my CustomAuthenticationProvider is not called as I expect it to be:
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Autowired
CredentialsService credentialsService;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String email = authentication.getName();
String password = authentication.getCredentials().toString();
List<SimpleGrantedAuthority> grantedAuthorities = new ArrayList<SimpleGrantedAuthority>();
User user = null;
try {
user = credentialsService.findByEmail(new EmailAddress(email));
} catch (IllegalArgumentException e) {
throw new BadCredentialsException("The login " + email + " and password could not match.");
}
if (user != null) {
if (credentialsService.checkPassword(user, password)) {
grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
return new UsernamePasswordAuthenticationToken(email, password, grantedAuthorities);
} else {
throw new BadCredentialsException("The login " + user.getEmail() + " and password could not match.");
}
}
throw new BadCredentialsException("The login " + authentication.getPrincipal() + " and password could not match.");
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
But the filter is exercised and a null token is found:
#Component
public class AuthenticationFromTokenFilter extends OncePerRequestFilter {
#Autowired
private TokenAuthenticationService tokenAuthenticationService;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
tokenAuthenticationService.authenticateFromToken(request);
chain.doFilter(request, response);
}
}
#Service
public class TokenAuthenticationServiceImpl implements TokenAuthenticationService {
private static Logger logger = LoggerFactory.getLogger(TokenAuthenticationServiceImpl.class);
private static final long ONE_WEEK = 1000 * 60 * 60 * 24 * 7;
private static final String TOKEN_URL_PARAM_NAME = "token";
#Autowired
private ApplicationProperties applicationProperties;
#Autowired
private UserDetailsService userDetailsService;
public void addTokenToResponseHeader(HttpHeaders headers, String username) {
String token = buildToken(username);
headers.add(CommonConstants.AUTH_HEADER_NAME, token);
}
public void addTokenToResponseHeader(HttpServletResponse response, Authentication authentication) {
String username = authentication.getName();
if (username != null) {
String token = buildToken(username);
response.addHeader(CommonConstants.AUTH_HEADER_NAME, token);
}
}
private String buildToken(String username) {
String token = null;
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (userDetails != null) {
Date expirationDate = new Date(System.currentTimeMillis() + ONE_WEEK);
token = CommonConstants.AUTH_BEARER + " " + Jwts.builder().signWith(HS256, getEncodedPrivateKey()).setExpiration(expirationDate).setSubject(userDetails.getUsername()).compact();
}
return token;
}
public Authentication authenticateFromToken(HttpServletRequest request) {
String token = extractAuthTokenFromRequest(request);
logger.debug("The request contained the JWT token: " + token);
if (token != null && !token.isEmpty()) {
try {
String username = Jwts.parser().setSigningKey(getEncodedPrivateKey()).parseClaimsJws(token).getBody().getSubject();
if (username != null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
logger.debug("Security - The filter authenticated fine from the JWT token");
}
} catch (SignatureException e) {
logger.info("The JWT token " + token + " could not be parsed.");
}
}
return null;
}
private String extractAuthTokenFromRequest(HttpServletRequest request) {
String token = null;
String header = request.getHeader(CommonConstants.AUTH_HEADER_NAME);
if (header != null && header.contains(CommonConstants.AUTH_BEARER)) {
int start = (CommonConstants.AUTH_BEARER + " ").length();
if (header.length() > start) {
token = header.substring(start - 1);
}
} else {
// The token may be set as an HTTP parameter in case the client could not set it as an HTTP header
token = request.getParameter(TOKEN_URL_PARAM_NAME);
}
return token;
}
private String getEncodedPrivateKey() {
String privateKey = applicationProperties.getAuthenticationTokenPrivateKey();
return Base64.getEncoder().encodeToString(privateKey.getBytes());
}
}
My security configuration is:
#Configuration
#EnableWebSecurity
#ComponentScan(nameGenerator = PackageBeanNameGenerator.class, basePackages = { "com.thalasoft.user.rest.security", "com.thalasoft.user.rest.filter" })
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private AuthenticationFromTokenFilter authenticationFromTokenFilter;
#Autowired
private SimpleCORSFilter simpleCORSFilter;
#Autowired
private RESTAuthenticationEntryPoint restAuthenticationEntryPoint;
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.authenticationProvider(new CustomAuthenticationProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling()
.authenticationEntryPoint(restAuthenticationEntryPoint)
.and()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(simpleCORSFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(authenticationFromTokenFilter, UsernamePasswordAuthenticationFilter.class)
.headers().cacheControl().disable().frameOptions().disable()
.and()
.userDetailsService(userDetailsService)
.authorizeRequests()
.antMatchers(RESTConstants.SLASH + UserDomainConstants.USERS + RESTConstants.SLASH + UserDomainConstants.LOGIN).permitAll()
.antMatchers(RESTConstants.SLASH + RESTConstants.ERROR).permitAll()
.antMatchers("/**").hasRole(UserDomainConstants.ROLE_ADMIN).anyRequest().authenticated();
}
}
The user details service is:
#Component
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
private CredentialsService credentialsService;
#Override
#Transactional
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if (username != null && !username.isEmpty()) {
User user = credentialsService.findByEmail(new EmailAddress(username));
if (user != null) {
return new UserDetailsWrapper(user);
}
}
throw new UsernameNotFoundException("The user " + username + " was not found.");
}
}
Why is the custom authentication provider not authenticating the username and password ?
UPDATE:
I read something interesting and puzzling in this guide
Note that the AuthenticationManagerBuilder is #Autowired into a method in a #Bean - that is what makes it build the global (parent) AuthenticationManager. In contrast if we had done it this way (using an #Override of a method in the configurer) then the AuthenticationManagerBuilder is only used to build a "local" AuthenticationManager, which is a child of the global one. In a Spring Boot application you can #Autowired the global one into another bean, but you can’t do that with the local one unless you explicitly expose it yourself.
So, is there anything wrong with my usage of the configure method for setting up the authenticationManagerBuilder.authenticationProvider(customAuthenticationProvider); ?
Instead of the above configuration, I tried the following configuration:
#Autowired
public void initialize(AuthenticationManagerBuilder authenticationManagerBuilder) {
authenticationManagerBuilder.authenticationProvider(customAuthenticationProvider);
}
But it still didn't exercise the custom authentication provider upon a request.
I also tried to have the filter after as in:
http.addFilterAfter(authenticationFromTokenFilter, UsernamePasswordAuthenticationFilter.class);
instead of addFilterBefore but it didn't change anything to the issue.
In WebSecurityConfiguration inside configure(HttpSecurity http) method:
http.authorizeRequests().antMatchers("/api/users/login").permitAll();
http.authorizeRequests().anyRequest().authenticated();
Add in the same order.
Explanation: Login and logout requests should be permitted without any authentication
A sample configure method that works is:
http.formLogin().disable().logout().disable().httpBasic().disable();
http.authorizeRequests().antMatchers("/logout", "/login", "/").permitAll();
http.authorizeRequests().anyRequest().authenticated();
http.addFilterBefore(new SomeFilter(), SecurityContextHolderAwareRequestFilter.class);
http.addFilterBefore(new CORSFilter(env), ChannelProcessingFilter.class);
http.addFilterBefore(new XSSFilter(),CORSFilter.class);
According to me when we implement our own ApplicationFilter by implementing GenericFilterBean we need to check if the token received from the request is valid or not. If it is not valid then we need to dump the token into the security context (for the authentication-provider to pick up). I haven't gone through your filter class. But this worked for me :
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httprequset=(HttpServletRequest)request;
String uname=request.getParameter("username");
String pwd=request.getParameter("password");
String role=request.getParameter("role");
List<GrantedAuthority> l = new ArrayList<>();
l.add( new SimpleGrantedAuthority(role.toUpperCase()) );
UsernamePasswordAuthenticationToken token=new UsernamePasswordAuthenticationToken(uname,pwd,l);
token.setAuthenticated(false);
SecurityContextHolder.getContext().setAuthentication(token);
chain.doFilter(httprequset, response);
}

Resources