I don't know why Resolved [org.springframework.security.access.AccessDeniedException: Access is denied] jwt? - spring-boot

After registering and logging in with postman, after verifying the accessToken, when I write a post, a rejection error occurs. I don't know why.
SecurityConfig
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailsService userDetailsService;
#Autowired
private JwtAuthenticationEntryPoint authenticationEntryPoint;
#Bean
public JwtAuthenticationFilter jwtAuthenticationFilter(){
return new JwtAuthenticationFilter();
}
#Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/api/**").permitAll()
.antMatchers("/api/auth/**").permitAll()
.anyRequest()
.authenticated();
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
JWTAuthResponse
#Getter
#Setter
public class JWTAuthResponse {
private String accessToken;
private String tokenType = "Bearer";
public JWTAuthResponse(String accessToken) {
this.accessToken = accessToken;
}
}
JwtAuthenticationFilter
public class JwtAuthenticationFilter extends OncePerRequestFilter {
#Autowired
private JwtTokenProvider tokenProvider;
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// get JWT (token) from http request
String token = getJWTfromRequest(request);
// validate token
if(StringUtils.hasText(token) && tokenProvider.validateToken(token)){
// get username from token
String username = tokenProvider.getUsernameFromJWT(token);
// load user associated with token
UserDetails userDetails = customUserDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities()
);
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// set spring security
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
filterChain.doFilter(request, response);
}
// Bearer <accessToken>
private String getJWTfromRequest(HttpServletRequest request){
String bearerToken = request.getHeader("Authorization");
if(StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")){
return bearerToken.substring(7, bearerToken.length());
}
return null;
}
}
AuthController
#RestController
#RequestMapping("/api/auth")
#RequiredArgsConstructor
public class AuthController {
private final AuthenticationManager authenticationManager;
private final UserRepository userRepository;
private final RoleRepository roleRepository;
private final PasswordEncoder passwordEncoder;
private final JwtTokenProvider tokenProvider;
#PostMapping("/signin")
public ResponseEntity<JWTAuthResponse> authenticateUser(#RequestBody LoginDto loginDto) {
Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
loginDto.getUsernameOrEmail(), loginDto.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
// get token from tokenProvider
String token = tokenProvider.generateToken(authentication);
return ResponseEntity.ok(new JWTAuthResponse(token));
}
#PostMapping("/signup")
public ResponseEntity<?> registerUser(#RequestBody SignUpDto signUpDto) {
// add check for username exists in a DB
if (userRepository.existsByUsername(signUpDto.getUsername())) {
return new ResponseEntity<>("Username is already taken!", HttpStatus.BAD_REQUEST);
}
// add check for email exists in a DB
if (userRepository.existsByEmail(signUpDto.getEmail())) {
return new ResponseEntity<>("Email is already taken!", HttpStatus.BAD_REQUEST);
}
// create user object
User user = new User();
user.setName(signUpDto.getName());
user.setUsername(signUpDto.getUsername());
user.setEmail(signUpDto.getEmail());
user.setPassword(passwordEncoder.encode(signUpDto.getPassword()));
Role roles = roleRepository.findByName("ROLE_ADMIN").isPresent() ? roleRepository.findByName("ROLE_ADMIN").get() : null;
userRepository.save(user);
return new ResponseEntity<>("User registered successfully", HttpStatus.OK);
}
PostController
#PreAuthorize("hasRole('ADMIN')")
#PostMapping
public ResponseEntity<PostDto> createPost(#Valid #RequestBody PostDto postDto) {
return new ResponseEntity<>(postService.createPost(postDto), HttpStatus.CREATED);
}
and my error is
Failed to authorize ReflectiveMethodInvocation: public org.springframework.http.ResponseEntity com.demo.blog.controller.PostController.createPost(com.demo.blog.dto.PostDto); target is of class [com.demo.blog.controller.PostController] with attributes [[authorize: 'hasRole('ADMIN')', filter: 'null', filterTarget: 'null']]
Resolved [org.springframework.security.access.AccessDeniedException: Access is denied]
I gave admin privileges when signing up, but I think it is a permission error.

Related

JWT authorization on spring security is throwing 403 Forbidden request

I am working on spring security with JWT. i am getting 403 forbidden error.
The OncePerRequest filter for my jwt token is as
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
if(authHeader!=null && !authHeader.equals("") && authHeader.startsWith("Bearer")){
String jwt = authHeader.substring(7);
if (jwt == null || jwt.isEmpty()){
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid JWT token in bearer header");
} else {
try{
String username = jwtUtil.validateTokenAndRetrieveSubject(jwt);
UserDetails userDetails = myUserDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
username,
userDetails.getPassword(),
userDetails.getAuthorities()
);
if (SecurityContextHolder.getContext().getAuthentication() == null){
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
System.out.println("this is from JWTFILTER -> " + username);
}catch (Exception e ){
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid JWT token: " +e);
}
}
}
filterChain.doFilter(request,response);
and my JWTUtil for token generation and validation.
#Component
public class JWTUtil {
#Value("${jwt-secret}")
private String SECRET_KEY;
#Value("${jwt-issuer}")
private String issuer;
#Value("${jwt-claim}")
private String UserName;
public String generateToken(String username) throws IllegalArgumentException, JWTCreationException{
return JWT.create()
.withClaim(UserName, username)
.withIssuedAt(new Date(System.currentTimeMillis()))
// .withExpiresAt(new Date(System.currentTimeMillis()/1000 + 3600 ))
.withIssuer(issuer)
.sign(Algorithm.HMAC256(SECRET_KEY));
}
public String validateTokenAndRetrieveSubject(String token) throws JWTVerificationException{
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET_KEY))
.withIssuer(issuer)
.build();
DecodedJWT jwt = verifier.verify(token);
return jwt.getClaim(UserName).asString();
}
}
and MyUserDetailsService. I don't have password for the user in my microservice so i am sending my username again org.springframework.security.core.userdetails.User().
#Component
public class MyUserDetailsService implements UserDetailsService {
#Autowired private UserRepo userRepo;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<User> userResource = userRepo.findUserByNameEquals(username);
if (!userResource.isPresent())
throw new UsernameNotFoundException("Could not found this username = " + username);
User user = userResource.get();
return new org.springframework.security.core.userdetails.User(
username,
user.getName(),
Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"))
);
}
}
and at the last the SecurityConfig in which i am using my custom authenticationEntryPoint.
#Component
#EnableWebSecurity
#AllArgsConstructor
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
#Autowired private MyUserDetailsService uds;
#Autowired private JWTFilter jwtFilter;
private MyAuthenticationEntryPoint myAuthenticationEntryPoint;
#Bean
protected SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception{
httpSecurity.csrf().disable().httpBasic().disable()
.cors()
.and()
.authorizeHttpRequests()
.antMatchers("/api/user/**").permitAll()
.antMatchers("/api/symbol/**").hasRole("ROLE_USER")
.and().userDetailsService(uds)
.exceptionHandling()
.authenticationEntryPoint(myAuthenticationEntryPoint)
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().formLogin().disable();
httpSecurity.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
return httpSecurity.build();
}
#Bean
public PasswordEncoder passwordEncoder (){
return new BCryptPasswordEncoder();
}
#Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception{
return authenticationConfiguration.getAuthenticationManager();
}
}
its my first time working on jwt so i have no idea about the issue.

Is there a different manner to do the authentication with the new spring security using jwt?

I try to do the security of my project adapting the baeldung documentation and the previous code, and I want to know the normal method of this. I have the following configuration
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig {
#Autowired
private UserDetailsService customUserDetailsService;
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
#Autowired
private PasswordEncoder passwordEncoder;
#Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}
#Bean
public AuthenticationManager authenticationManager(HttpSecurity httpSecurity) throws Exception{
return httpSecurity.getSharedObject(AuthenticationManagerBuilder.class)
.userDetailsService(customUserDetailsService)
.passwordEncoder(passwordEncoder)
.and()
.build();
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf()
.disable()
.cors()
.and()
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/user/create", "/api/login", "/swagger-ui/**", "/v3/api-docs/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.httpBasic();
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
then I have this token provider
#Component
public class JtwTokenProvider {
#Value("${app.jwt-secret}")
private String jwtSecret;
#Value("${app.jwt-expiration-milliseconds}")
private int jwtExpirationInMs;
public String generateToken(Authentication authentication) {
String username = authentication.getName();
Date now = new Date();
Date expiryDate = new Date(now.getTime() + jwtExpirationInMs);
String token = Jwts.builder().setSubject(username).setIssuedAt(new Date())
.setExpiration(expiryDate).signWith(SignatureAlgorithm.HS512, jwtSecret).compact();
return token;
}
public String getUsernameOfJwt(String token){
Claims claims = Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody();
return claims.getSubject();
}
public boolean validateToken(String token){
try {
Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token);
return true;
}catch (SignatureException e){
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid JWT signature");
}catch (MalformedJwtException e){
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid JWT token");
}catch (ExpiredJwtException e){
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Expired JWT token");
}catch (UnsupportedJwtException e){
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Unsupported JWT token");
}catch (IllegalArgumentException e){
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "JWT claims string is empty");
}
}
}
The token filter is the same and this manner is okay? What others exist?
public class JwtAuthenticationFilter extends OncePerRequestFilter {
#Autowired
private JtwTokenProvider tokenProvider;
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//Get JWT from request
String token = getJwtFromRequest(request);
if (StringUtils.hasText(token) && tokenProvider.validateToken(token)) {
// Get username from token
String username = tokenProvider.getUsernameOfJwt(token);
// Load user associated with the token
UserDetails userDetails = customUserDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
//Set the authentication in the SecurityContext
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
private String getJwtFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.replace("Bearer ", "");
}
return null;
}
}
Then I used the userdetails service
#Service
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username).orElseThrow(
() -> new NotFoundException("User not found"));
return new org.springframework.security.core.userdetails.User(user.getUsername(),
user.getPassword(), getAuthority(user.getRoles()));
}
private Collection<? extends GrantedAuthority> getAuthority(Set<Role> roles){
return roles.stream().map(role -> new SimpleGrantedAuthority(role.getName()))
.collect(Collectors.toList());
}
}
Finally here is my authentication, this is safe? what is the standart manner of do the same?
#Service
public class LoginServiceImpl implements LoginService{
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private JtwTokenProvider jwtTokenProvider;
#Override
public JwtAuthResponse login(LoginRequest loginRequest) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginRequest.getUsername(),
loginRequest.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = jwtTokenProvider.generateToken(authentication);
return new JwtAuthResponse(jwt);
}
}
I was waiting your responses thanks :D

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

Spring Security and Token Auth for API

I've been scouring the web looking at a lot of different ways to implement token based authentication using Spring & Spring Security (SS). I'm not really wanting to go the full on Oauth route so I've been trying to do something and keep things pretty simple.
What I want is to pass a username/password to SS's built in mechanism and on success, generate a token that I pass back to the user. The user then makes all future requests with the token in a custom header. The token will expire after som length of time. I am aware this is kind of what Oauth does but again, don't want to use it.
So I've got something kind of working. I can login with username/password and get the token back. I can then make requests with the token successfully. What isn't working are authorities. Here's what I'm doing...
Custom Auth Failure Handler that simply returns HttpServletResponse.SC_UNAUTHORIZED
Custom Success Handler that simply returns HttpServletResponse.SC_OK and the token
Custom Auth Entry Point that just response with HttpServletResponse.SC_UNAUTHORIZED
Now, I also have a custom UserDetails and UserDetailsService.
public class MyUserDetails implements UserDetails {
private User user; // this is my own User object
private List<GrantedAuthority> authorities;
public MyUserDetails(User user, List<GrantedAuthority> authorities) {
this.user = user;
this.authorities = authorities;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
#Override
public String getPassword() {
return user.getPassword();
}
#Override
public String getUsername() {
return user.getUsername();
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public void setAuthorities(List<GrantedAuthority> authorities) {
this.authorities = authorities;
}
}
#Service
public class MyUserDetailsService implements UserDetailsService {
private final UserService userService;
#Autowired
public MyUserDetailsService(UserService userService) {
this.userService = userService;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userService.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException(username);
}
List<GrantedAuthority> authorities = new ArrayList<>();
// for now, just add something
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
return new MyUserDetails(user, authorities);
}
}
In order to look in the header for the token and tell spring all is well, I created a AuthTokenFilter...
public class AuthTokenFilter extends UsernamePasswordAuthenticationFilter {
#Autowired
private MyUserDetailsService userDetailsService;
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String authToken = httpRequest.getHeader("X-TOKEN-AUTH");
String username = null;
if (authToken != null) {
username = Jwts.parser()
.setSigningKey("1234")
.parseClaimsJws(authToken)
.getBody()
.getSubject();
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// TODO: validate token
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
}
And this is how I've configured web security:
#Configuration
#EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private MyUserDetailsService userDetailsService;
#Autowired
private RestAuthEntryPoint authenticationEntryPoint;
#Autowired
private AuthSuccessHandler authSuccessHandler;
#Autowired
private AuthFailureHandler authFailureHandler;
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
#Override
public UserDetailsService userDetailsServiceBean() throws Exception {
return super.userDetailsServiceBean();
}
#Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService);
authenticationProvider.setPasswordEncoder(new ShaPasswordEncoder());
return authenticationProvider;
}
#Bean
public AuthTokenFilter authenticationTokenFilterBean() throws Exception {
AuthTokenFilter authenticationTokenFilter = new AuthTokenFilter();
authenticationTokenFilter.setAuthenticationManager(authenticationManagerBean());
return authenticationTokenFilter;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
#Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authenticationProvider(authenticationProvider())
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.formLogin()
.permitAll()
.loginProcessingUrl("/login")
.usernameParameter("username")
.passwordParameter("password")
.successHandler(authSuccessHandler)
.failureHandler(authFailureHandler)
.and()
.logout()
.permitAll()
.and()
.sessionManagement()
.maximumSessions(1);
http.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
http.authorizeRequests().anyRequest().authenticated();
}
}
Everything seems to work accept SS isn't limiting access at all. If the token is there SS just lets everything pass.
Well, after much trial and error it was as simple as adding the following to my SpringSecurityConfig
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
Kind of surprised that I didn't run into this sooner. Not sure if this is something somewhat new or what.

Resources