Reputation: 759
How to implement JWT based authentication and authorization in Spring Security
I am trying to implement jwt based authentication and authorization in my spring boot app. I followed a tutorial written here. But it does not do anything in my app. It does not return jwt token rather I am authenticated and my request is fulfilled. I am new to spring security. here is my code.
I want my app return jwt token and using the token the requests must be authorized.
Here is my code.
JWTAuthenticationFilter.java
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
@Autowired
CustomUserDetailsService userService;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
try {
CustomUserDetails user = new ObjectMapper().readValue(request.getInputStream(), CustomUserDetails.class);
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), new ArrayList<>()));
} catch (Exception e) {
}
return super.attemptAuthentication(request, response);
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication auth) {
String loggedInUser = ((CustomUserDetails) auth.getPrincipal()).getUsername();
Claims claims = Jwts.claims().setSubject(loggedInUser);
if (loggedInUser != null) {
CustomUserDetails user = (CustomUserDetails) userService.loadUserByUsername(loggedInUser);
String roles[] = {};
for (Role role : user.getUser().getUserRoles()) {
roles[roles.length + 1] = role.getRole();
}
claims.put("roles", roles);
claims.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME));
}
String token = Jwts.builder().setClaims(claims)
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SECRET.getBytes()).compact();
response.addHeader(HEADER_STRING, TOKEN_PREFIX + token);
}
}
JWTAuthorizationFilter.java
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
public JWTAuthorizationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
String header = request.getHeader(HEADER_STRING);
if (header == null || !header.startsWith(TOKEN_PREFIX)) {
chain.doFilter(request, response);
return;
}
UsernamePasswordAuthenticationToken authentication = getToken(request);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
}
@SuppressWarnings("unchecked")
private UsernamePasswordAuthenticationToken getToken(HttpServletRequest request) {
String token = request.getHeader(HEADER_STRING);
System.out.println("-----------------------------------------------------");
System.out.println("Token: " + token);
System.out.println("-----------------------------------------------------");
if (token != null) {
Claims claims = Jwts.parser().setSigningKey(SECRET.getBytes())
.parseClaimsJws(token.replace(TOKEN_PREFIX, "")).getBody();
String user = claims.getSubject();
ArrayList<String> roles = (ArrayList<String>) claims.get("roles");
ArrayList<MyGrantedAuthority> rolesList = new ArrayList<>();
if (roles != null) {
for (String role : roles) {
rolesList.add(new MyGrantedAuthority(role));
}
}
if (user != null) {
return new UsernamePasswordAuthenticationToken(user, null, null);
}
return null;
}
return null;
}
}
SecurityConfig.java
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Qualifier("userDetailsService")
@Autowired
CustomUserDetailsService userDetailsService;
@Autowired
PasswordEncoder passwordEncoder;
@Autowired
AuthenticationManager authenticationManager;
@Autowired
JWTAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
try {
auth.userDetailsService(this.userDetailsService).passwordEncoder(passwordEncoder);
} catch (Exception e) {
}
}
/*
* @Autowired public void configureGlobal(AuthenticationManagerBuilder auth)
* throws Exception {
* auth.inMemoryAuthentication().withUser("student").password("student").roles(
* "student").and().withUser("admin") .password("admin").roles("admin"); }
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
// http.authorizeRequests().anyRequest().permitAll();
// http.authorizeRequests().antMatchers("/api/**").permitAll();
http.addFilter(new JWTAuthenticationFilter(authenticationManager));
http.addFilter(new JWTAuthorizationFilter(authenticationManager));
http.authorizeRequests().antMatchers("/api/student/**").hasAnyRole("STUDENT", "ADMIN");
http.authorizeRequests().antMatchers("/api/admin/**").hasRole("ADMIN");
http.authorizeRequests().antMatchers("/api/libararian/**").hasAnyRole("LIBRARIAN", "ADMIN");
http.authorizeRequests().antMatchers("/api/staff/**").hasAnyRole("STAFF", "ADMIN");
http.authorizeRequests().antMatchers("/api/teacher/**").hasAnyRole("TEACHER", "ADMIN");
http.authorizeRequests().antMatchers("/api/parent/**").hasAnyRole("PARENT", "ADMIN");
http.httpBasic().authenticationEntryPoint(jwtAuthenticationEntryPoint);
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// http.formLogin().and().logout().logoutSuccessUrl("/login?logout").permitAll();
}
}
MyGrantedAuthority.java
public class MyGrantedAuthority implements GrantedAuthority {
String authority;
MyGrantedAuthority(String authority) {
this.authority = authority;
}
@Override
public String getAuthority() {
// TODO Auto-generated method stub
return authority;
}
}
JWTAuthenticationEntryPoint.java
@Component
public class JWTAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception)
throws IOException, ServletException {
response.setStatus(403);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
String message;
if (exception.getCause() != null) {
message = exception.getCause().getMessage();
} else {
message = exception.getMessage();
}
byte[] body = new ObjectMapper().writeValueAsBytes(Collections.singletonMap("error", message));
response.getOutputStream().write(body);
}
}
Upvotes: 4
Views: 15034
Reputation: 759
I got it. I followed another tutorial which made my job easy. Here is the complete rewrite working code
TokenProvider.java
package com.cloudsofts.cloudschool.security;
import static com.cloudsofts.cloudschool.security.SecurityConstants.EXPIRATION_TIME;
import static com.cloudsofts.cloudschool.security.SecurityConstants.SECRET;
import java.util.ArrayList;
import java.util.Date;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import com.cloudsofts.cloudschool.people.users.pojos.CustomUserDetails;
import com.cloudsofts.cloudschool.people.users.pojos.Role;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
@Component
public class TokenProvider {
@Autowired
CustomUserDetailsService userService;
public String createToken(String username) {
CustomUserDetails user = (CustomUserDetails) userService.loadUserByUsername(username);
Claims claims = Jwts.claims().setSubject(username);
ArrayList<String> rolesList = new ArrayList<String>();
for (Role role : user.getUser().getUserRoles()) {
rolesList.add(role.getRole());
}
claims.put("roles", rolesList);
String token = Jwts.builder().setClaims(claims)
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)).setIssuedAt(new Date())
.signWith(SignatureAlgorithm.HS512, SECRET).compact();
return token;
}
public Authentication getAuthentication(String token) {
String username = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody().getSubject();
UserDetails userDetails = this.userService.loadUserByUsername(username);
return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
}
}
JWTFilter.java
package com.cloudsofts.cloudschool.security;
import static com.cloudsofts.cloudschool.security.SecurityConstants.HEADER_STRING;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.GenericFilterBean;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.UnsupportedJwtException;
public class JWTFilter extends GenericFilterBean {
public final static String AUTHORIZATION_HEADER = "Authorization";
private final TokenProvider tokenProvider;
public JWTFilter(TokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
try {
HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
String jwt = resolveToken(httpRequest);
if (jwt != null) {
Authentication authentication = this.tokenProvider.getAuthentication(jwt);
if (authentication != null) {
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
chain.doFilter(servletRequest, servletResponse);
} catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException | SignatureException
| UsernameNotFoundException e) {
// Application.logger.info("Security exception {}", e.getMessage());
((HttpServletResponse) servletResponse).setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
}
private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader(HEADER_STRING);
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7, bearerToken.length());
}
return null;
}
}
JWTConfigurer.java
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
public class JWTConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private final TokenProvider tokenProvider;
public JWTConfigurer(TokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
@Override
public void configure(HttpSecurity http) throws Exception {
JWTFilter customFilter = new JWTFilter(tokenProvider);
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
}
}
SecurityConfig.java
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.cloudsofts.cloudschool.people.users.pojos.User;
@RestController
public class LoginController {
private AuthenticationManager authenticationManager;
private TokenProvider tokenProvider;
private CustomUserDetailsService userService;
LoginController(AuthenticationManager auth, CustomUserDetailsService service, TokenProvider tokenProvider) {
this.authenticationManager = auth;
this.userService = service;
this.tokenProvider = tokenProvider;
}
@PostMapping("/login")
public String getToken(@RequestBody User user, HttpServletResponse response) {
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(user.getUsername(),
user.getPassword());
authenticationManager.authenticate(authToken);
return tokenProvider.createToken(user.getUsername());
}
}
LoginController.java
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.cloudsofts.cloudschool.people.users.pojos.User;
@RestController
public class LoginController {
private AuthenticationManager authenticationManager;
private TokenProvider tokenProvider;
private CustomUserDetailsService userService;
LoginController(AuthenticationManager auth, CustomUserDetailsService service, TokenProvider tokenProvider) {
this.authenticationManager = auth;
this.userService = service;
this.tokenProvider = tokenProvider;
}
@PostMapping("/login")
public String getToken(@RequestBody User user, HttpServletResponse response) {
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(user.getUsername(),
user.getPassword());
authenticationManager.authenticate(authToken);
return tokenProvider.createToken(user.getUsername());
}
}
Link to the Github project that helped me.
Upvotes: 5
Reputation: 303
I am also using jwt authentication on my project and I could see that you are missing an entry point which should be used on the project. I will tell you how I implemented it and see if it can help you =). You need to implement an authenticationEntryPoint in order to tell the code how the authentication will be done. It can be added after the filters, on the http.authorizerequest, with the command:
.authenticationEntryPoint(jwtAuthEndPoint);
where jwtAuthEndPoint is the following component:
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
AuthenticationException e) throws IOException, ServletException {
httpServletResponse.setStatus(SC_FORBIDDEN);
httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
String message;
if (e.getCause() != null) {
message = e.getCause().getMessage();
} else {
message = e.getMessage();
}
byte[] body = new ObjectMapper().writeValueAsBytes(Collections.singletonMap("error", message));
httpServletResponse.getOutputStream().write(body);
}
}
I would also suggest you to take a look on this tutorial, which helped me A LOT in this case: https://sdqali.in/blog/2016/07/07/jwt-authentication-with-spring-web---part-4/
Upvotes: 2