Can't authenticate or authorize with JWT - spring

public class JWTTokenGeneratorFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (null != authentication) {
SecretKey key = Keys.hmacShaKeyFor(SecurityConstants.JWT_KEY.getBytes(StandardCharsets.UTF_8));
String jwt = Jwts.builder().setIssuer("Eazy Bank").setSubject("JWT Token")
.claim("username", authentication.getName())
.claim("authorities", populateAuthorities(authentication.getAuthorities()))
.setIssuedAt(new Date())
.setExpiration(new Date((new Date()).getTime() + 30000000))
.signWith(key).compact();
response.setHeader(SecurityConstants.JWT_HEADER, jwt);
}
filterChain.doFilter(request, response);
}
#Override
protected boolean shouldNotFilter(HttpServletRequest request) {
return !request.getServletPath().equals("/user");
}
private String populateAuthorities(Collection<? extends GrantedAuthority> collection) {
Set<String> authoritiesSet = new HashSet<>();
for (GrantedAuthority authority : collection) {
authoritiesSet.add(authority.getAuthority());
}
return String.join(",", authoritiesSet);
}
}
This is my jwtgenerator class
public class JWTTokenValidatorFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String jwt = request.getHeader(SecurityConstants.JWT_HEADER);
if (null != jwt) {
try {
SecretKey key = Keys.hmacShaKeyFor(
SecurityConstants.JWT_KEY.getBytes(StandardCharsets.UTF_8));
Claims claims = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(jwt)
.getBody();
String username = String.valueOf(claims.get("username"));
String authorities = (String) claims.get("authorities");
Authentication auth = new UsernamePasswordAuthenticationToken(username, null,
AuthorityUtils.commaSeparatedStringToAuthorityList(authorities));
SecurityContextHolder.getContext().setAuthentication(auth);
} catch (Exception e) {
throw new BadCredentialsException("Invalid Token received!");
}
}
filterChain.doFilter(request, response);
}
#Override
protected boolean shouldNotFilter(HttpServletRequest request) {
return request.getServletPath().equals("/user");
}
}
this is my jwt validator class
Configuration
public class ProjectSecurityConfig {
#Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler();
requestHandler.setCsrfRequestAttributeName("_csrf");
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.cors().configurationSource(new CorsConfigurationSource() {
#Override
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(Collections.singletonList("http://localhost:4200"));
config.setAllowedMethods(Collections.singletonList("*"));
config.setAllowCredentials(true);
config.setAllowedHeaders(Collections.singletonList("*"));
config.setExposedHeaders(Arrays.asList("Authorization"));
config.setMaxAge(3600L);
return config;
}
}).and().csrf((csrf) -> csrf.csrfTokenRequestHandler(requestHandler).ignoringRequestMatchers("/contact","/register")
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()))
.addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class)
.addFilterBefore(new RequestValidationBeforeFilter(), BasicAuthenticationFilter.class)
.addFilterAt(new AuthoritiesLoggingAtFilter(),BasicAuthenticationFilter.class)
.addFilterAfter(new AuthoritiesLoggingAfterFilter(), BasicAuthenticationFilter.class)
.addFilterAfter(new JWTTokenGeneratorFilter(), BasicAuthenticationFilter.class)
.addFilterBefore(new JWTTokenValidatorFilter(), BasicAuthenticationFilter.class)
.authorizeHttpRequests()
.requestMatchers("/myAccount").hasRole("ADMIN")
.requestMatchers("/myBalance").hasAnyRole("USER","ADMIN")
.requestMatchers("/myLoans").hasRole("USER")
.requestMatchers("/myCards").hasRole("USER")
.requestMatchers("/user").authenticated()
.requestMatchers("/notices","/contact","/register").permitAll()
.and().formLogin()
.and().httpBasic();
return http.build();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
this is configuration
and this is interface
public interface SecurityConstants {
public static final String JWT_KEY = "jxgEQeXHuPq8VdbyYFNkANdudQ53YUn4";
public static final String JWT_HEADER = "Authorization";
}
problem is i cant authorize on my endpoint for example localhost:8080/myAccount?id=1

Related

Spring Security For Authorities Based Filtering Using Custom Http Header

I am trying to implement RBAC using Spring Security. User authentication is implemented separately and sessionId is generated for the app to use. I wanted to have Spring Security take the sessionId from the Http Header and would use the sessionId to get the Authorities from a database to determine whether the user is authorized to access certain endpoints. The problem is that I don't know how to get the authorities from the database on demand and I don't know if the configuration is being done correctly. This is what I have so far:
#Configuration
#EnableWebSecurity
public class CustomSecurityFilter {
#Bean
AuthenticationManager customAuthenticationManager(HttpHeaderAuthenticationProvider httpHeaderAuthenticationProvider) {
return new ProviderManager(List.of(httpHeaderAuthenticationProvider));
}
#Bean
HttpHeaderAuthenticationProvider newHttpHeaderAuthenticationProvider() {
return new HttpHeaderAuthenticationProvider();
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http,
AuthenticationManager authenticationManager) throws Exception {
http.addFilterBefore(getFilter(authenticationManager), AnonymousAuthenticationFilter.class).authorizeRequests()
.antMatchers(HttpMethod.GET, "/api/apples").hasAuthority("viewApples")
.antMatchers(HttpMethod.POST, "/api/apples").hasAuthority("createApples")
return http.build();
}
private Filter getFilter(AuthenticationManager authenticationManager) {
return new HttpHeaderProcessingFilter(
new OrRequestMatcher(
new AntPathRequestMatcher("/api/apples/**"),
),
authenticationManager
);
}
}
public class HttpHeaderAuthenticationProvider implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
var sessionId = ((String) authentication.getPrincipal());
// Somehow connect to database to get session and authorities information?
boolean isValid = sessionId != null;
if (isValid) {
return newPreAuthenticatedToken("sessionId", List.of());
} else {
throw new AccessDeniedException("Invalid sessionId");
}
}
#Override
public boolean supports(Class<?> authentication) {
return PreAuthenticatedAuthenticationToken.class.equals(authentication);
}
public static PreAuthenticatedAuthenticationToken newPreAuthenticatedToken(String userId, List<String> permissions) {
var grantedAuthorityList = new ArrayList<GrantedAuthority>();
for (String permission : permissions) {
grantedAuthorityList.add(new SimpleGrantedAuthority(permission));
}
return new PreAuthenticatedAuthenticationToken(userId, null, grantedAuthorityList);
}
}
public class HttpHeaderProcessingFilter extends AbstractAuthenticationProcessingFilter {
public HttpHeaderProcessingFilter(RequestMatcher requiresAuthenticationRequestMatcher,
AuthenticationManager authenticationManager) {
super(requiresAuthenticationRequestMatcher);
setAuthenticationManager(authenticationManager);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
return getAuthenticationManager().authenticate(
// Not sure if we are supposed to do this
HttpHeaderAuthenticationProvider.newPreAuthenticatedToken("sessionId", List.of())
);
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException {
SecurityContextHolder.getContext().setAuthentication(authResult);
chain.doFilter(request, response);
}
}
I tried using these resources:
https://salahuddin-s.medium.com/custom-header-based-authentication-using-spring-security-17f4163d0986
https://www.baeldung.com/spring-security-granted-authority-vs-role
I was also wondering whether JWT would be a good candidate to use in place of a custom sessionId with RBAC + Session Handling.
I was able to configure the filter to use authorities. Here is what I have:
#Component
#Slf4j
public class CustomPreAuthProvider implements AuthenticationProvider {
private boolean throwExceptionWhenTokenRejected;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (!this.supports(authentication.getClass())) {
return null;
} else {
log.debug(String.valueOf(LogMessage.format("PreAuthenticated authentication request: %s", authentication)));
if (authentication.getPrincipal() == null) {
log.debug("No pre-authenticated principal found in request.");
if (this.throwExceptionWhenTokenRejected) {
throw new BadCredentialsException("No pre-authenticated principal found in request.");
} else {
return null;
}
} else if (authentication.getCredentials() == null) {
log.debug("No pre-authenticated credentials found in request.");
if (this.throwExceptionWhenTokenRejected) {
throw new BadCredentialsException("No pre-authenticated credentials found in request.");
} else {
return null;
}
} else if (!authentication.isAuthenticated()) {
throw new InsufficientAuthenticationException("Access token likely no longer valid.");
}
return authentication;
}
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(PreAuthenticatedAuthenticationToken.class);
}
public void setThrowExceptionWhenTokenRejected(boolean throwExceptionWhenTokenRejected) {
this.throwExceptionWhenTokenRejected = throwExceptionWhenTokenRejected;
}
}
#Service
public class CustomUserDetails implements UserDetailsService {
#Autowired
private SessionRepository sessionRepository;
#Autowired
private RoleRepository roleRepository;
#Autowired
private AuthHelper authHelper;
#Override
public UserDetails loadUserByUsername(String sessionId) throws UsernameNotFoundException, IllegalStateException {
var sessions = sessionRepository.getSession(sessionId); // Database query for session information
if (sessions == null || sessions.isEmpty()) {
throw new UsernameNotFoundException("Session Not Found");
} else if (sessions.size() > 1) {
throw new IllegalStateException("More than one record with sessionId found");
}
var session = sessions.get(0);
var authoritySet = new HashSet<String>();
for (String role : session.getRoles()) {
var authorities = roleRepository.getUserPrivilegesByRoleName(role); // Database query for authorities
for (UserRolePrivilege userRolePrivilege : authorities) {
authoritySet.add(userRolePrivilege.getPermittedAction());
}
}
var grantedAuthority = new ArrayList<GrantedAuthority>();
for (String authority : authoritySet) {
grantedAuthority.add(new SimpleGrantedAuthority(authority));
}
var introspect = authHelper.validateAccessToken(session.getSessionId(), session.getAccessToken(),
session.getRefreshToken(), session.getExpirationTime()); // Code to verify token
var user = new UserImpl();
user.setUsername(session.getEmail());
user.setPassword(session.getAccessToken());
user.setEnabled(introspect.getIntrospect().isActive());
user.setAccountNonExpired(introspect.getIntrospect().isActive());
user.setAccountNonLocked(introspect.getIntrospect().isActive());
user.setCredentialsNonExpired(introspect.getIntrospect().isActive());
user.setAuthorities(grantedAuthority);
return user;
}
}
public class SessionAuthFilter extends AbstractAuthenticationProcessingFilter {
private final CustomUserDetails customUserDetails;
protected SessionAuthFilter(RequestMatcher requestMatcher, AuthenticationManager authenticationManager,
CustomUserDetails customUserDetails) {
super(requestMatcher, authenticationManager);
this.customUserDetails = customUserDetails;
this.setContinueChainBeforeSuccessfulAuthentication(true);
this.setAuthenticationSuccessHandler((request, response, authentication) -> {});
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
var sessionId = request.getHeader("sessionId") != null ? request.getHeader("sessionId").trim() : null;
var user = customUserDetails.loadUserByUsername(sessionId);
var authentication = new PreAuthenticatedAuthenticationToken(user.getUsername(), user.getPassword(),
user.getAuthorities());
authentication.setAuthenticated(user.isCredentialsNonExpired());
authentication.setDetails(customUserDetails);
SecurityContextHolder.getContext().setAuthentication(authentication);
return this.getAuthenticationManager().authenticate(authentication);
}
}
#Configuration
#EnableWebSecurity
public class SecurityConfig {
#Bean
AuthenticationManager customAuthenticationManager(CustomPreAuthProvider preAuthProvider) {
return new ProviderManager(List.of(preAuthProvider));
}
#Bean
SessionAuthFilter customAuthFilter(AuthenticationManager authManager, CustomUserDetails customUserDetails) {
return new SessionAuthFilter(
new OrRequestMatcher(
new AntPathRequestMatcher("/apples/**"),
),
authManager,
customUserDetails);
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http, SessionAuthFilter authFilter) throws Exception {
http.exceptionHandling()
.authenticationEntryPoint(new Http403ForbiddenEntryPoint())
.accessDeniedHandler(new AccessDeniedHandlerImpl())
.and()
.formLogin().disable()
.httpBasic().disable()
.authorizeRequests()
.antMatchers(
"/",
"/error",
"/v3/api-docs/**",
"/swagger-ui/**",
"/swagger-ui.html",
"/actuator/**"
).permitAll()
.antMatchers(HttpMethod.GET, "/apples").hasAuthority("viewApples")
.antMatchers(HttpMethod.POST, "/apples").hasAuthority("createApples")
.anyRequest().authenticated()
.and()
.addFilterBefore(authFilter, AbstractPreAuthenticatedProcessingFilter.class)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
return http.build();
}
}

Add additional user requirements in spring security login and handle various exceptions

I am new to Spring security, I have implemented a basic user login functionality for my app using JWT. Aside from checking for username and password at login I would like to add other parameters such as a "account is verified" boolean condition but I am not sure where to add this requirement. Additionally, I need to return a 403 forbidden response status message if the "account is verified" condition is false and return a different response status message if the username password combination isn't found at all. Here Is the code I currently have which correctly handles the login of an existing user (without checking for the "account is verified" condition) and always throws a 401 when the user is found. Any feedback would be helpful.
WebSecurityConfigurerAdapter
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final ApplicationUserDetailsService applicationUserDetailsService;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
public WebSecurityConfig(ApplicationUserDetailsService userDetailsService) {
this.applicationUserDetailsService = userDetailsService;
this.bCryptPasswordEncoder = new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.cors()
.and()
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.addFilter(new AuthenticationFilter(authenticationManager()))
.addFilter(new AuthorizationFilter(authenticationManager()))
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Bean
public PasswordEncoder encoder() {
return this.bCryptPasswordEncoder;
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(applicationUserDetailsService)
.passwordEncoder(bCryptPasswordEncoder);
}
}
UserDetailsService
public class ApplicationUserDetailsService implements UserDetailsService {
private final ApplicationUserRepository applicationUserRepository;
public ApplicationUserDetailsService(ApplicationUserRepository applicationUserRepository) {
this.applicationUserRepository = applicationUserRepository;
}
#Override
public UserDetails loadUserByUsername(String nickname)
throws UsernameNotFoundException, UserIsNotActiveException {
Optional<ApplicationUser> applicationUser =
applicationUserRepository.findByNickname(nickname);
if (!applicationUser.isPresent()) {
throw new UsernameNotFoundException(nickname);
}
return new User(
applicationUser.get().getNickname(),
applicationUser.get().getPassword(),
emptyList());
}
}
AuthenticationFilter
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
public AuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
throws AuthenticationException {
try {
ApplicationUser applicationUser =
new ObjectMapper().readValue(req.getInputStream(), ApplicationUser.class);
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
applicationUser.getNickname(),
applicationUser.getPassword(),
new ArrayList<>()));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
#Override
protected void successfulAuthentication(
HttpServletRequest req,
HttpServletResponse res,
FilterChain chain,
Authentication auth) {
Date exp = new Date(System.currentTimeMillis() + EXPIRATION_TIME);
Key key = Keys.hmacShaKeyFor(KEY.getBytes());
Claims claims = Jwts.claims().setSubject(((User) auth.getPrincipal()).getUsername());
String token =
Jwts.builder()
.setClaims(claims)
.signWith(key, SignatureAlgorithm.HS512)
.setExpiration(exp)
.compact();
res.addHeader("token", token);
}
}
AuthorizationFilter
public AuthorizationFilter(AuthenticationManager authManager) {
super(authManager);
}
#Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
String header = request.getHeader(HEADER_NAME);
if (header == null) {
chain.doFilter(request, response);
return;
}
UsernamePasswordAuthenticationToken authentication = authenticate(request);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
}
private UsernamePasswordAuthenticationToken authenticate(HttpServletRequest request) {
String token = request.getHeader(HEADER_NAME);
if (token != null) {
Jws<Claims> user =
Jwts.parserBuilder()
.setSigningKey(Keys.hmacShaKeyFor(KEY.getBytes()))
.build()
.parseClaimsJws(token);
if (user != null) {
return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
} else {
return null;
}
}
return null;
}
ApplicationUser
public class ApplicationUser {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#Column(unique = true)
String email;
#Column(unique = true)
String nickname;
String biography;
String password; // Hashed
#Builder.Default boolean isActive = false;
}
The interface UserDetails (that is returned by the UserDetailsService) has some utility methods that can help you with it.
While the account is not activated, you can return false from the UserDetails#isEnabled method, or maybe you can use UserDetails#isAccountNonLocked as well.
Those methods will then be automatically validated on the AbstractUserDetailsAuthenticationProvider$Default(Pre/Post)AuthenticationChecks class.
After the user goes through the activation flow, you can change the property to true and it will allow the user to authenticate.
Tip: add the logging.level.org.springframework.security=TRACE to your application.properties to help to debug.

403 Forbidden on using JWT Authorization in springboot

I was trying to implement basic authentication and authorization in springboot. But i am getting 403 Forbidden error when i send a get request to an endpoint.I have already added JWT token in Authorization header. I am trying to send a Get request to "/user".
Here's the code which I have written
UserController
#RequestMapping(value = "/user")
#RestController
public class UserController {
#Autowired
UserService userService;
#PostMapping
public UserDetailsResponseModel createUser(#RequestBody UserDetailsRequestModel userRequestObject)
{
UserDetailsResponseModel userResponse = new UserDetailsResponseModel();
UserDTO userDto = new UserDTO();
BeanUtils.copyProperties(userRequestObject,userDto);
UserDTO createdUser = userService.createUser(userDto);
BeanUtils.copyProperties(createdUser,userResponse);
return userResponse;
}
#GetMapping
public String getUser()
{
return "Get user was called";
}
}
WebSecurity Class
#Configuration
#EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
private final UserService userService;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
public WebSecurity(UserService userService,BCryptPasswordEncoder bCryptPasswordEncoder)
{
this.userService = userService;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.POST, SecurityConstants.SIGN_UP_URL).permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new AuthenticationFilter(authenticationManager()))
.addFilter(new AuthorizationFilter(authenticationManager()));
}
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(bCryptPasswordEncoder);
}
}
Authorization Filter
public class AuthorizationFilter extends BasicAuthenticationFilter {
public AuthorizationFilter(AuthenticationManager authenticationManager)
{
super(authenticationManager);
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String header = request.getHeader(SecurityConstants.HEADER_STRING);
if(header == null || !header.startsWith(SecurityConstants.TOKEN_PREFIX));
{
chain.doFilter(request,response);
}
UsernamePasswordAuthenticationToken authenticationToken = getAuthentication(request);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
chain.doFilter(request,response);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
String token = request.getHeader(SecurityConstants.HEADER_STRING);
token = token.replace(SecurityConstants.TOKEN_PREFIX,"");
String user = Jwts.parser()
.setSigningKey(SecurityConstants.TOKEN_SECRET)
.parseClaimsJws(token)
.getBody()
.getSubject();
if(user != null){
return new UsernamePasswordAuthenticationToken(user,null,new ArrayList<>());
}
return null;
}
}
Authentication Filter
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
public AuthenticationFilter(AuthenticationManager authenticationManager)
{
this.authenticationManager = authenticationManager;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
try {
UserLoginRequestModel creds = new ObjectMapper().readValue(request.getInputStream(),UserLoginRequestModel.class);
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
creds.getEmail(),
creds.getPassword(),
new ArrayList<>()
)
);
}
catch (IOException ex){
throw new RuntimeException(ex);
}
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
String userName = ((User) authResult.getPrincipal()).getUsername();
String token = Jwts.builder()
.setSubject(userName)
.setExpiration(new Date(System.currentTimeMillis()+SecurityConstants.EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512,SecurityConstants.TOKEN_SECRET)
.compact();
UserService userService = (UserService) SpringApplicationContext.getBean("userServiceImplementation");
UserDTO userDTO = userService.getUser(userName);
response.addHeader(SecurityConstants.HEADER_STRING,SecurityConstants.TOKEN_PREFIX+token);
response.addHeader("UserID",userDTO.getUserId());
}
}

Getting null token in Jwt token filter when a request is sent in?

Just started using Jwt tokens when securing my microservices and keep getting a null token in my JwtTokenFilter class when a request is sent in but don't know where from, and finding it hard to understand why?
JwtTokenFilter.class
public class JwtTokenFilter extends OncePerRequestFilter {
private JwtTokenProvider jwtTokenProvider;
public JwtTokenFilter(JwtTokenProvider jwtTokenProvider) {
this.jwtTokenProvider = jwtTokenProvider;
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String token = jwtTokenProvider.resolveToken(request);
System.out.println("Token: " + token);
try {
if (token != null && jwtTokenProvider.validateToken(token)) {
Authentication auth = jwtTokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(auth);
}
} catch (CustomException ex) {
SecurityContextHolder.clearContext();
response.sendError(ex.getHttpStatus().value(), ex.getMessage());
return;
}
filterChain.doFilter(request, response);
}
SecurityConfig.class
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtTokenProvider jwtTokenProvider;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.and()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/auth/login").permitAll()
.antMatchers("/auth/register").permitAll()
.antMatchers("/auth/{username}").permitAll()
.anyRequest()
.authenticated();
http.addFilterBefore(new JwtTokenFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class);
}
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}
}
JwtTokenProvider.class
#Component
public class JwtTokenProvider {
#Value("$security.jwt.token.secret-key")
private String secretKey;
private long validityInMilliseconds = 3600000;
#Autowired
private CustomUserDetails customUserDetails;
#PostConstruct
protected void init() {
secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
}
public String createToken(String username, List<Role> roles) {
Claims claims = Jwts.claims().setSubject(username);
claims.put("auth", roles.stream().map(s -> new SimpleGrantedAuthority(s.getAuthority())).collect(Collectors.toList()));
Date now = new Date();
Date validity = new Date(now.getTime() + validityInMilliseconds);
return Jwts.builder()//
.setClaims(claims)//
.setIssuedAt(now)//
.setExpiration(validity)//
.signWith(SignatureAlgorithm.HS256, secretKey)//
.compact();
}
public Authentication getAuthentication(String token) {
UserDetails userDetails = customUserDetails.loadUserByUsername(getUsername(token));
return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
}
public String getUsername(String token) {
return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
}
public String resolveToken(HttpServletRequest req) {
String bearerToken = req.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
throw new CustomException("Expired or invalid JWT token", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
If you need any more classes shown just ask, thanks for the help.
Realised the problem was how I registered it with my Gateway microservice in the spring configuration. All sorted thanks for the help.

Spring Session + REST + Custom Authentication Filter(read credentials from JSON rather query param)

I'm trying to convert my rest services authentication from basic authentication to form based authentication the below code works fine(Note I've commented out custom authentication filter) if I send authentication details in url as query parameter something like this http://localhost:8080/login?username=dfdf&&password=sdsdd
However I'm not keen on sending credentials as query parameter instead I would prefer to send it as json, Hence I've created custom authentication filter. When I add the custom authentication filter, my spring session stops working. I cannot find x-auth-token field in my response header. Any suggestion how to enable spring session and custom authentication together/or may be easier way to handle json input for credentials.
#Configuration
#EnableWebSecurity
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private ObjectMapper objectMapper;
#Bean
public PasswordEncoder passwordEncoder() {
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
#Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
builder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/", "/register", "/index.html").permitAll().and().authorizeRequests()
.anyRequest().authenticated().and().requestCache().requestCache(new NullRequestCache()).and()
.formLogin().failureHandler(getRESTAuthenticationFailureHandler())
.successHandler(getRESTAuthenticationSuccessHandler()).usernameParameter("username")
.passwordParameter("password").and().exceptionHandling()
.authenticationEntryPoint(getRESTAuthenticationEntryPoint()).and()
//.addFilter(getAuthenticationFilter())
.csrf().disable();
}
#Bean
public HttpSessionStrategy httpSessionStrategy() {
return new HeaderHttpSessionStrategy();
}
#Bean
public RESTAuthenticationEntryPoint getRESTAuthenticationEntryPoint() {
return new RESTAuthenticationEntryPoint();
}
#Bean
public RESTAuthenticationSuccessHandler getRESTAuthenticationSuccessHandler() {
return new RESTAuthenticationSuccessHandler();
}
#Bean
public RESTAuthenticationFailureHandler getRESTAuthenticationFailureHandler() {
return new RESTAuthenticationFailureHandler();
}
#Bean
public AuthenticationFilter getAuthenticationFilter() {
AuthenticationFilter filter = new AuthenticationFilter();
try {
filter.setAuthenticationManager(this.authenticationManager());
} catch (Exception e) {
e.printStackTrace();
}
return filter;
}
public class RESTAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
}
public class RESTAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
clearAuthenticationAttributes(request);
}
}
public class RESTAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
super.onAuthenticationFailure(request, response, exception);
}
}
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final Logger LOG = LoggerFactory.getLogger(AuthenticationFilter.class);
private boolean postOnly = true;
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
String username = null;
String password = null;
UserDetails userDetails = null;
if ("application/json".equals(request.getHeader("Content-Type"))) {
userDetails = getJson(request);
if (userDetails != null) {
username = userDetails.getUsername();
}
} else {
username = obtainUsername(request);
}
if ("application/json".equals(request.getHeader("Content-Type"))) {
if (userDetails != null) {
password = userDetails.getPassword();
}
} else {
password = obtainPassword(request);
}
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username,
password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
private UserDetails getJson(HttpServletRequest request) {
try {
final List<String> data = IOUtils.readLines(request.getReader());
final String jsonData = data.stream().collect(Collectors.joining());
LOG.info(jsonData);
UserDetails userDetails = objectMapper.readValue(jsonData, UserDetails.class);
return userDetails;
} catch (IOException e) {
LOG.error("Failed to read data {}", e.getMessage(), e);
return null;
}
}
}
}
As suggested I've created custom filter which converts json object to request parameter and adds to HttpServletRequest, However are there any inbuilt spring security custom filters to do the same job?
#Configuration
#EnableWebSecurity
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private ObjectMapper objectMapper;
#Bean
public FilterRegistrationBean contextFilterRegistrationBean() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
CstFilter contextFilter = new CstFilter();
registrationBean.addUrlPatterns("/login");
registrationBean.setFilter(contextFilter);
registrationBean.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
return registrationBean;
}
#Bean
public PasswordEncoder passwordEncoder() {
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
#Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
builder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.exceptionHandling()
.authenticationEntryPoint(getRESTAuthenticationEntryPoint())
.and()
.formLogin()
.permitAll()
.loginProcessingUrl("/login")
.failureHandler(getRESTAuthenticationFailureHandler())
.successHandler(getRESTAuthenticationSuccessHandler())
.usernameParameter("username")
.passwordParameter("password")
.and()
.logout()
.permitAll()
.logoutSuccessHandler(getRESTLogoutSuccessHandler())
.and()
.authorizeRequests()
.antMatchers("/", "/index.html")
.permitAll()
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.requestCache()
.requestCache(new NullRequestCache());
}
#Bean
public HttpSessionStrategy httpSessionStrategy() {
return new HeaderHttpSessionStrategy();
}
#Bean
public RESTAuthenticationEntryPoint getRESTAuthenticationEntryPoint() {
return new RESTAuthenticationEntryPoint();
}
#Bean
public RESTAuthenticationSuccessHandler getRESTAuthenticationSuccessHandler() {
return new RESTAuthenticationSuccessHandler();
}
#Bean
public RESTAuthenticationFailureHandler getRESTAuthenticationFailureHandler() {
return new RESTAuthenticationFailureHandler();
}
#Bean
public RESTLogoutSuccessHandler getRESTLogoutSuccessHandler() {
return new RESTLogoutSuccessHandler();
}
public class JsonConvertFilter extends HttpServletRequestWrapper {
private final Logger LOG = LoggerFactory.getLogger(JsonConvertFilter.class);
private UserDetails userDetails;
public JsonConvertFilter(HttpServletRequest request) {
super((HttpServletRequest)request);
userDetails = getJson();
}
public String getParameter(String key){
if(userDetails!=null){
if("username".equals(key)){
return userDetails.getUsername();
}
if("password".equals(key)){
return userDetails.getPassword();
}
}
System.out.println("Called wrapper");
return super.getParameter(key);
}
private UserDetails getJson() {
try {
final List<String> data = IOUtils.readLines(super.getReader());
final String jsonData = data.stream().collect(Collectors.joining());
LOG.info(jsonData);
UserDetails userDetails = objectMapper.readValue(jsonData, UserDetails.class);
return userDetails;
} catch (IOException e) {
LOG.warn("Failed to read data {}", e.getMessage(), e);
return null;
}
}
}
public class CstFilter implements Filter{
#Override
public void destroy() {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
chain.doFilter(new JsonConvertFilter((HttpServletRequest)request), response);
}
#Override
public void init(FilterConfig arg0) throws ServletException {
}
}
public class RESTLogoutSuccessHandler implements LogoutSuccessHandler{
#Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
String uri = request.getRequestURI();
if ("logout".equals(uri)) {
response.sendError(HttpServletResponse.SC_OK, "Succesfully Logged out");
}
}
}
public class RESTAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().print("Unauthorizated....");
}
}
public class RESTAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
clearAuthenticationAttributes(request);
}
}
public class RESTAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
String uri = request.getRequestURI();
if ("logout".equals(uri)) {
response.sendError(HttpServletResponse.SC_OK, "Succesfully Logged out");
} else {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
"Authentication Failed: " + exception.getMessage());
}
}
}
}
For any one want to test this using jquery ajax.
/* Alerts the results */
$.ajax({
url : "login",
type : "POST",
async : false,
contentType: "application/json",
data : "{ \"username\":\""+username+"\", \"password\":\"" + password + "\"}",
success : function(data, status, request) {
alert("Success");
authtokenKey = request.getResponseHeader('x-auth-token');
},
error : function(xhr, status, error) {
alert(error);
}
});

Resources