Spring Security multiple configuration - spring

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

Related

SpringBoot different auths (MS AD & JWT) to one Controller

I tried to implement small API Gateway for my Mobile App on Spring Boot.
In my architecture i uses MS Active Directory Server for auth staff of company and in future will sms verify code for clients company for sending JWT.
I'm not use layer DAO, UsersRepository and DB connect.
All HTTP requests sending via RestTemplate from Services layer to our inthernal CRM-system.
I implements LDAP AD auth is very simple HttpBasic configuration bellow:
#Configuration
#EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and().csrf()
.disable()
.and()
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/api/v1/send/**").permitAll()
.anyRequest()
.authenticated()
.and()
.httpBasic();
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider = new
ActiveDirectoryLdapAuthenticationProvider("mydomain.com", "ldap://192.168.0.100:389/");
activeDirectoryLdapAuthenticationProvider.setConvertSubErrorCodesToExceptions(true);
activeDirectoryLdapAuthenticationProvider.setUseAuthenticationRequestCredentials(true);
activeDirectoryLdapAuthenticationProvider.setSearchFilter("(&(objectClass=user)(userPrincipalName={0})(memberOf=CN=mobileaccess,OU=User Groups,OU=DomainAccountsUsers,DC=MYDOMAIN,DC=COM))");
auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider);
auth.eraseCredentials(true);
}
}
I have two RestController V1 and V2 for example:
#RequestMapping("api/v1")
//get token for staff (AD user) HttpBasic auth
#PostMapping("auth/get/stafftoken")
public ResponseEntity<?> getToken() {
// some code...
HttpHeaders tokenHeaders = new HttpHeaders();
tokenHeaders.setBearerAuth(tokenAuthenticationService.getToken());
return new ResponseEntity<>(tokenHeaders, HttpStatus.OK);
}
//get JWT if code from sms == code in my CRM-system (for client) not auth - permitAll
#PostMapping("send/clienttoken")
public #ResponseStatus
ResponseEntity<?> sendVerifyCode(#RequestParam("verifycode") String verifycode) {
// some code...
HttpHeaders tokenHeaders = new HttpHeaders();
tokenHeaders.setBearerAuth(tokenAuthenticationService.getToken());
return new ResponseEntity<>(tokenHeaders, HttpStatus.OK);
}
#RequestMapping("api/v2")
#GetMapping("get/contract/{number:[0-9]{6}")
public Contract getContract(#PathVariable String number) {
return contractsService.getContract(number);
}
How to implements Bearer Auth requests to Controller APIv2 with JWT tokens (clients and staff)?
I think this is implemented through filter chain?
So guys
If you implements multi authentification as in my example, first of all create utility class for builds token and validation users JWT. This is standard code, for example:
public static String createUserToken(Authentication authentication) {
return Jwts.builder()
.setSubject(authentication.getName())
.claim(authentication.getAuthorities())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SIGN_KEY)
.compact();
}
public static Authentication getAuthentication(HttpServletRequest request) {
String token = extractJwt(request);
try {
if (token != null) {
Claims claims = Jwts.parser().setSigningKey(SIGN_KEY).parseClaimsJws(token).getBody();
String username = Jwts.parser().setSigningKey(SIGN_KEY).parseClaimsJws(token).getBody().getSubject();
return username != null ? new UsernamePasswordAuthenticationToken(username, "", Collections.EMPTY_LIST) : null;
}
} catch (ExpiredJwtException e) {
}
return null;
}
Аfter you should create two filters:
LoginAuthentificationFilter extends BasicAuthenticationFilter
JwtAuthenticationFilter extends GenericFilterBean
Code example below
public class LoginAuthentificationFilter extends BasicAuthenticationFilter {
public LoginAuthentificationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
super.doFilterInternal(request, response, chain);
}
}
public class JwtAuthenticationFilter extends GenericFilterBean {
private RequestMatcher requestMatcher;
public JwtAuthenticationFilter(String path) {
this.requestMatcher = new AntPathRequestMatcher(path);
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
if (!requiresAuthentication((HttpServletRequest) servletRequest)) {
filterChain.doFilter(servletRequest, servletResponse);
return;
}
Authentication authentication = JwtUtils.getAuthentication((HttpServletRequest) servletRequest);
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(servletRequest, servletResponse);
}
private boolean requiresAuthentication(HttpServletRequest request) {
return requestMatcher.matches(request);
}
}
And at the end
Settings WebSecurityConfigurerAdapter
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and().csrf().disable()
.exceptionHandling().authenticationEntryPoint(new JwtAuthenticationEntryPoint())
.and()
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/api/v1/noauth_endpoints").permitAll()
.anyRequest()
.authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterAt(jwtFilter(), BasicAuthenticationFilter.class)
.addFilter(loginFilter());
http.headers().cacheControl();
}
Beans
#Bean
public LoginAuthentificationFilter loginFilter() {
return new LoginAuthentificationFilter(authenticationManager());
}
#Bean
public JwtAuthenticationFilter jwtFilter() {
return new JwtAuthenticationFilter("/api/v2/**");
}
#Bean
#Override
public AuthenticationManager authenticationManager() {
return new ProviderManager(Arrays.asList(activeDirectoryLdapAuthenticationProvider()));
}
#Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider("bzaimy.com", "ldap://192.168.0.100:389/");
provider.setSearchFilter("(&(objectClass=user)(userPrincipalName={0})(memberOf=CN=mobileaccess,OU=User Groups,OU=DomainAccountsUsers,DC=MYDOMAIN,DC=COM))");
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}

Access Deny and Allow Functionality using Spring and Spring security

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!

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.

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**

Spring security using JWT, how to exclude certain endpoints like /login from being authenticated using JwtAuthenticationFilter?

I want to exclude /login url from being authenticated by spring security.
My configuration class looks like'
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests().antMatchers("/v1/pricing/login").permitAll()
.antMatchers("v1/pricing/**").authenticated().and()
.addFilterBefore(corsFilter,UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/v1/pricing/login");
}
JwtAuthenticationFilter looks like
- commented the exception part, as it starts throwing exception in login also
#Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(JwtAuthenticationFilter.class);
#Autowired
JwtTokenProvider jwtTokenProvider;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String jwt = getJwtFromRequest(request);
if (StringUtils.hasText(jwt) && jwtTokenProvider.validateToken(jwt)) {
String[] userInfo = jwtTokenProvider.getUserDetailsFromJWT(jwt);
UserDetails userDetails = new UserPrincipal(Long.parseLong(userInfo[0]), userInfo[1], userInfo[2], null,
userInfo[3]);
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(userDetails, null, null);
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
filterChain.doFilter(request, response);
}
private String getJwtFromRequest(HttpServletRequest request) {
String token = request.getHeader("Authorization");
if (StringUtils.hasText(token)) {
return token;
} /*else {
throw new AuthenticationServiceException("Authorization header cannot be blank!");
}*/
return null;
}
}
Any request with /v1/pricing/login still goes to JWtAuthentication filter and fails.
JwtTokenAuthenticationProcessingFilter filter is configured to skip following endpoints: /api/auth/login and /api/auth/token. This is achieved with SkipPathRequestMatcher implementation of RequestMatcher.
public class SkipPathRequestMatcher implements RequestMatcher {
private OrRequestMatcher matchers;
private RequestMatcher processingMatcher;
public SkipPathRequestMatcher(List<String> pathsToSkip, String processingPath) {
Assert.notNull(pathsToSkip);
List<RequestMatcher> m = pathsToSkip.stream().map(path -> new AntPathRequestMatcher(path)).collect(Collectors.toList());
matchers = new OrRequestMatcher(m);
processingMatcher = new AntPathRequestMatcher(processingPath);
}
#Override
public boolean matches(HttpServletRequest request) {
if (matchers.matches(request)) {
return false;
}
return processingMatcher.matches(request) ? true : false;
}
}
Then call :
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
public static final String JWT_TOKEN_HEADER_PARAM = "X-Authorization";
public static final String FORM_BASED_LOGIN_ENTRY_POINT = "/api/auth/login";
public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/api/**";
public static final String TOKEN_REFRESH_ENTRY_POINT = "/api/auth/token";
protected JwtTokenAuthenticationProcessingFilter buildJwtTokenAuthenticationProcessingFilter() throws Exception {
List<String> pathsToSkip = Arrays.asList(TOKEN_REFRESH_ENTRY_POINT, FORM_BASED_LOGIN_ENTRY_POINT);
SkipPathRequestMatcher matcher = new SkipPathRequestMatcher(pathsToSkip, TOKEN_BASED_AUTH_ENTRY_POINT);
JwtTokenAuthenticationProcessingFilter filter
= new JwtTokenAuthenticationProcessingFilter(failureHandler, tokenExtractor, matcher);
filter.setAuthenticationManager(this.authenticationManager);
return filter;
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(ajaxAuthenticationProvider);
auth.authenticationProvider(jwtAuthenticationProvider);
}
#Bean
protected BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable() // We don't need CSRF for JWT based authentication
.exceptionHandling()
.authenticationEntryPoint(this.authenticationEntryPoint)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(FORM_BASED_LOGIN_ENTRY_POINT).permitAll() // Login end-point
.antMatchers(TOKEN_REFRESH_ENTRY_POINT).permitAll() // Token refresh end-point
.antMatchers("/console").permitAll() // H2 Console Dash-board - only for testing
.and()
.authorizeRequests()
.antMatchers(TOKEN_BASED_AUTH_ENTRY_POINT).authenticated() // Protected API End-points
.and()
.addFilterBefore(buildAjaxLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(buildJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class);
}
}

Resources