I try to replace BeanUtils.copyProperties with ModelMapper.map(). I get error when try to login with spring security:
1) Converter org.modelmapper.internal.converter.MergingCollectionConverter#2a5749f5 failed to convert org.hibernate.collection.internal.PersistentBag to java.util.List.
1 error
at org.modelmapper.internal.Errors.throwMappingExceptionIfErrorsExist(Errors.java:380) ~[modelmapper-2.3.7.jar:na]
at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:81) ~[modelmapper-2.3.7.jar:na]
at org.modelmapper.ModelMapper.mapInternal(ModelMapper.java:573) ~[modelmapper-2.3.7.jar:na]
at org.modelmapper.ModelMapper.map(ModelMapper.java:406) ~[modelmapper-2.3.7.jar:na]
at com.thao.wsapplication.service.impl.UserServiceImpl.getUser(UserServiceImpl.java:81) ~[main/:na]
at com.thao.wsapplication.security.AuthenticationFilter.successfulAuthentication(AuthenticationFilter.java:71) ~[main/:na]
function getUser()
#Override
public UserDto getUser(String email) {
UserDto returnValue = new UserDto();
UserEntity userEntity = userRepository.findByEmail(email);
if (userEntity == null) throw new UsernameNotFoundException(email);
returnValue = new ModelMapper().map(userEntity, UserDto.class);
// BeanUtils.copyProperties(userEntity, returnValue);
return returnValue;
}
function successfulAuthentication()
#Override
protected void successfulAuthentication(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain,
Authentication auth) throws IOException, ServletException {
String userName = ((User) auth.getPrincipal()).getUsername();
String token = Jwts.builder()
.setSubject(userName)
.setExpiration(new Date(System.currentTimeMillis() + SecurityConstants.EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SecurityConstants.getTokenSecret())
.compact();
UserService userService = (UserService)SpringApplicationContext.getBean("userServiceImpl");
UserDto userDto = userService.getUser(userName);
res.addHeader(SecurityConstants.HEADER_STRING, SecurityConstants.TOKEN_PREFIX + token);
res.addHeader("UserID", userDto.getUserId());
}
update my source code: source code
Related
I have two spring apps: Users-App(login/registration, etc.) and main app. I need to access main app via JWT. How can I send token from users-app to main-app by using "Simple" Controller?
My Controller(users-app):
#Controller
#RequiredArgsConstructor
public class UserController {
private final UserService userService;
#Autowired
private UserRegistrationValidator userValidator;
#Autowired
private LoginValidator loginValidator;
#GetMapping("/")
public String startPage() {
return "redirect:/index";
}
#GetMapping("/index")
public String homePage() {
return "home";
}
#GetMapping("/users")
public String getUsers() {
return "redirect:http://localhost:8080/";
}
#GetMapping("/login")
public String loginPage(HttpServletRequest request, Model model) {
User user = new User();
model.addAttribute("user", user);
if (isCookiesExists(request)) return "redirect:/users";
return "login";
}
#PostMapping("/signin")
public String loginUser(#ModelAttribute("user") User user, BindingResult bindingResult) {
loginValidator.validate(user, bindingResult);
if (bindingResult.hasErrors()) {
return "login";
}
return "redirect:/users";
}
#GetMapping("/register")
public String registerUser(HttpServletRequest request, Model model) {
User user = new User();
model.addAttribute("my_user", user);
if (isCookiesExists(request)) return "redirect:/users";
return "register";
}
#PostMapping("/register")
public String saveUser(#ModelAttribute("my_user") User user, BindingResult bindingResult) {
userValidator.validate(user, bindingResult);
if (bindingResult.hasErrors()) {
return "register";
}
userService.saveUser(user);
return "redirect:/login";
}
private boolean isCookiesExists(HttpServletRequest request) {
if (request.getCookies() != null) {
List<String> auth = Arrays.stream(request.getCookies())
.filter(cookie -> cookie.getName().equals("auth"))
.map(Cookie::getValue)
.filter(Objects::nonNull).collect(Collectors.toList());
return auth.size() != 0;
}
return false;
}
}
Creating tokens:
#Service
#RequiredArgsConstructor
#Transactional
#Slf4j
public class GetTokenServiceImpl implements GetTokenService {
private final PasswordEncoder passwordEncoder;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private UserService userService;
#Override
public String createToken(HttpServletRequest request, User user) {
Algorithm algorithm = Algorithm.HMAC512("my_secret_key_10210_oqpowqkq192199qkkwoxa");
return JWT.create()
.withSubject(user.getUsername())
.withExpiresAt(new Date(System.currentTimeMillis() + 30 * 60 * 1000))
.withIssuer(request.getRequestURL().toString())
.withClaim(
"roles",
user.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList())
)
.sign(algorithm);
}
#Override
public String createRefreshToken(HttpServletRequest request, User user) {
Algorithm algorithm = Algorithm.HMAC512("my_secret_key_10210_oqpowqkq192199qkkwoxa");
return JWT.create()
.withSubject(user.getUsername())
.withExpiresAt(new Date(System.currentTimeMillis() + 60 * 60 * 10000))
.withIssuer(request.getRequestURL().toString())
.sign(algorithm);
}
#Override
public Map<String, String> getTokens(HttpServletRequest request, String username, String password) {
com.user.app.server.model.User myUser = userService.getUser(username);
if (myUser == null || password == null || !passwordEncoder.matches(password, myUser.getPassword()) ) {
log.error("Error logging in: {} ", "Bad Credentials error");
}
User user = (User) userDetailsService.loadUserByUsername(username);
String accessToken = createToken(request, user);
String refreshToken = createRefreshToken(request, user);
Map<String, String> tokens = new HashMap<>();
tokens.put("access_token", accessToken);
tokens.put("refresh_token", refreshToken);
return tokens;
}
}
And my authorization filter:
#Slf4j
public class CustomAuthorizationFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String header = "";
if (request.getServletPath().equals("/users")){
header = request.getHeader(AUTHORIZATION);
}
if (header != null && header.startsWith("Bearer ")) {
try {
String token = header.substring("Bearer ".length());
Algorithm algorithm = Algorithm.HMAC512("my_secret_key_10210_oqpowqkq192199qkkwoxa");
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT decodedJWT = verifier.verify(token);
String username = decodedJWT.getSubject();
String[] roles = decodedJWT.getClaim("roles").asArray(String.class);
Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
stream(roles).forEach(s -> authorities.add(new SimpleGrantedAuthority(s)));
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(username, null, authorities);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request, response);
} catch (Exception exception) {
log.error("Error logging in: {}", exception.getMessage());
response.setHeader("error", exception.getMessage());
response.setStatus(FORBIDDEN.value());
Map<String, String> error = new HashMap<>();
error.put("error_message", exception.getMessage());
response.setContentType(APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(response.getOutputStream(), error);
}
} else {
filterChain.doFilter(request, response);
}
}
}
Ps: Actually I did this by saving the token in cookies, but I didn't think this is a good practice. This is additional filter in users-app:
#Component
#RequiredArgsConstructor
public class CustomHeaderFilter implements Filter {
private final GetTokenService tokenService;
private Map<String, String> tokens = new HashMap<>();
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp =(HttpServletResponse) response;
if (req.getServletPath().equals("/signin")) {
tokens = tokenService
.getTokens(req, req.getParameter("username"), req.getParameter("password"));
}
if (req.getServletPath().equals("/users")) {
String value = "Bearer " + tokens.get("access_token");
resp.setHeader(AUTHORIZATION, value);
Cookie cookies = new Cookie("auth", tokens.get("access_token"));
cookies.setPath("/");
cookies.setMaxAge(1800);
resp.addCookie(cookies);
}
chain.doFilter(req, response);
}
}
And this is how I get from cookies(in main app):
#Slf4j
public class CustomAuthorizationFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws IOException {
String header = request.getHeader(AUTHORIZATION);
Cookie[] cookies = request.getCookies();
if (cookies != null) {
String[] info = stream(cookies).map(Cookie::getValue).toArray(String[]::new);
header = "Bearer " + info[0];
}
try {
String token = header.substring("Bearer ".length());
Algorithm algorithm = Algorithm.HMAC512("my_secret_key_10210_oqpowqkq192199qkkwoxa");
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT decodedJWT = verifier.verify(token);
String username = decodedJWT.getSubject();
String[] roles = decodedJWT.getClaim("roles").asArray(String.class);
Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
stream(roles).forEach(s -> authorities.add(new SimpleGrantedAuthority(s)));
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(username, null, authorities);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request, response);
} catch (Exception exception) {
log.error("Error logging in: {}", exception.getMessage());
response.setHeader("error", exception.getMessage());
response.setStatus(FORBIDDEN.value());
Map<String, String> error = new HashMap<>();
error.put("error_message", exception.getMessage());
response.setContentType(APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(response.getOutputStream(), error);
}
}
}
Inside getUserObject() method we are not able to get Authentication object. It's available for 1st request only. But its setting to null for subsequent requests from client. So please help me to configure it properly so that its available for all the requests calls.
I am not sure how to configure inside configure method in AuthConfig.java so that authentication object would be available for all the requests chain
AuthConfig.java:
#Configuration
#EnableWebSecurity
public class AuthConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/callback", "/", "/auth0/authorize", "/resources/**", "/public/**", "/static/**",
"/login.do", "/logout.do", "/thankYou.do", "/customerEngagement.do",
"/oldCustomerEngagement.do", "/registerNew.do", "/forgotPassword.do", "/checkMongoService.do",
"/reset.do", "/rlaLogin.do", "/fnfrefer.do", "/thankYouLeadAggregator.do", "/referral")
.permitAll()
.anyRequest().authenticated().and().
logout()
.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler());
}
------------------------------------------------------------------------------
AuthController.java:
#RequestMapping(value = "/callback", method = RequestMethod.GET)
public void callback(HttpServletRequest request, HttpServletResponse response)
throws IOException, IdentityVerificationException {
try {
Tokens tokens = authenticationController.handle(request, response);
DecodedJWT jwt = JWT.decode(tokens.getIdToken());
List<GrantedAuthority> grantedAuths = new ArrayList<GrantedAuthority>();
grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER"));
Authentication auth = new UsernamePasswordAuthenticationToken(jwt.getSubject(), jwt.getToken(), grantedAuths);
SecurityContext context = SecurityContextHolder.getContext();
context.setAuthentication(auth);
response.sendRedirect(config.getContextPath(request) + "/loancenter/home.do");
} catch (Exception e) {
LOG.info("callback page error");
response.sendRedirect(config.getContextPath(request) + "/loancenter");
}
}
--------------------------------------------------------------------------------
HomeController.java:
#Controller
public class DefaultController implements InitializingBean {
#RequestMapping(value = "home.do")
public ModelAndView showCustomerPage(HttpServletRequest req, HttpServletResponse res, Model model) {
ModelAndView mav = new ModelAndView();
try {
User user = getUserObject(req);
if(user==null) {
LOG.info("User not found in session");
mav.setViewName(JspLookup.LOGIN);
return mav;
}
} catch (Exception e) {
LOG.error("Exception in Home page ", e);
}
return mav;
}
protected User getUserObject(HttpServletRequest request) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
LOG.info("authentication::{}", authentication);
User user = null;
if (authentication == null) {
return user;
}
if (authentication.getPrincipal() instanceof User) {
user = (User) authentication.getPrincipal();
LOG.info("User already authenticated and logging :{}", user.getEmailId());
sendUserLoginEmailToLO(user);
} else {
UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;
DecodedJWT jwt = JWT.decode(token.getCredentials().toString());
user = userProfileDao.findByUserEmail(jwt.getClaims().get("email").asString());
if (user != null) {
LOG.info("First time authentication:{}", user.getEmailId());
boolean auth0EmailVerified = jwt.getClaims().get("email_verified").asBoolean();
LOG.info("First time authentication email verified flag from auth0:{}", auth0EmailVerified);
LOG.info("First time authentication email verified flag from nlc:{}", user.getEmailVerified());
if (BooleanUtils.isFalse(user.getEmailVerified()) && auth0EmailVerified) {
LOG.info("Email is verified in Auth0, updating email_verified flag to true in DB for userId: {}",
user.getId());
userProfileDao.verifyEmail(user.getId());
LOG.info("First time authentication updated email verified flag in nlc db:{}", user.getEmailId());
}
if (user.getNewEmailVerified() != null && BooleanUtils.isFalse(user.getNewEmailVerified())) {
LOG.info("The user is verifying his email: set his verified to true");
userProfileDao.verifyNewEmail(user.getId());
}
Authentication auth = new UsernamePasswordAuthenticationToken(user, jwt.getToken(),
token.getAuthorities());
messageServiceHelper.checkIfUserFirstLogin(user);
LOG.info("Authentication provided for user : {}", user.getEmailId());
LOG.debug("Auth object constructed : {}", auth);
SecurityContextHolder.getContext().setAuthentication(auth);
HttpSession session = request.getSession(true);
session.setAttribute("SPRING_SECURITY_CONTEXT", SecurityContextHolder.getContext());
sendUserLoginEmailToLO(user);
}
}
return user;
}
}
I have a following method in the controller
#GetMapping("/hello")
#PreAuthorize("hasRole('ADMIN')")
public String hello() {
return "Hello " + JWTRequestFilter.UserClaim;
}
When a user who has the ADMIN role tries to access the /hello, 403 is returned. I have enabled the following in the websecurity class.
#EnableGlobalMethodSecurity(prePostEnabled = true)
Below is the JWT token.
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzZW50aHVyYW4iLCJSb2xlcyI6WyJBRE1JTiIsIlVTRVIiXSwiZXhwIjoxNTkzMDE0NDE5LCJpYXQiOjE1OTI5Nzg0MTl9.-7lTav3Nux8WVafUBGXjOxtXcE-r0fpfjb7wM7hrg6w
Even the JWT token has the role but still i'm getting 403. Does this preauthorize annotation see the role from the JWT or does it make a DB call and check the role of a user.Even I have used the #PreAuthrize annotation but still getting the same behaviour. How to resolve this 403. Below I have attached the JWTRequestFilter class.
public class JWTRequestFilter extends OncePerRequestFilter {
#Autowired
private MyUserDetailService userDetailService;
#Autowired
private JWTUtil jwtUtil;
public static String UserClaim = "";
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
if(authorizationHeader != null && authorizationHeader.startsWith("Bearer ")){
jwt = authorizationHeader.substring(7);
username = jwtUtil.extractUsername(jwt);
UserClaim = username;
}
if(username != null && SecurityContextHolder.getContext().getAuthentication() == null){
UserDetails userDetails= this.userDetailService.loadUserByUsername(username);
if (jwtUtil.validateToken(jwt, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
This is how I'm generating the JWT token and how I set the roles.
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
Set<String> Userroles = new HashSet<>();
User user = userRepository.findByUsername(userDetails.getUsername());
for(Role role:user.getRoles()){
Userroles.add(role.getName());
}
claims.put("Roles",Userroles.toArray());
return createToken(claims, userDetails.getUsername());
}
Suggested Approach to identify the issue
if(username != null && SecurityContextHolder.getContext().getAuthentication() == null){
UserDetails userDetails= this.userDetailService.loadUserByUsername(username);
if (jwtUtil.validateToken(jwt, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
String authorities = userDetails.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.joining());
System.out.println("Authorities granted : " + authorities);
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
} else {
System.out.println("Not Valid Token);
}
} else {
System.out.println("No Token);
}
Outcome: Token was valid but authorities were not loaded
Authorities granted :
Suggested Solution
Fix the MyUserDetailService to load Authorities in userDetails
Spring adds the prefix ROLE_ to the authorities.
You can either implement a setter that appends the role prefix.
Or another much simple way to do it would be to have a separate classe that implements GrantedAuthority interface
public class UserRole implements GrantedAuthority {
private MyRole role;
#Override
public String getAuthority() {
return "ROLE_" + role.toString();
}
}
//MyRole is the enum with the different roles ADMIN,VIEWER,...
I'm writing Spring Boot REST API, and I'm using JWT tokens. Now, I'm trying to create role-based authorization.
This is the tutorial/implementation that I'm using.
https://auth0.com/blog/implementing-jwt-authentication-on-spring-boot/
I expanded this implementation with additional Role entity, and added #ManyToMany mapping to ApplicationUser Entity.
Now, as far as I understands, user roles should be added to token (during its creation).
So, this is an existing code:
#Override
protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain, Authentication auth) throws IOException, ServletException {
String token = JWT.create()
.withSubject(((User) auth.getPrincipal()).getUsername())
.withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.sign(HMAC512(SECRET.getBytes()));
res.addHeader(HEADER_STRING, TOKEN_PREFIX + token);
}
I guess user roles should be added there. There is a function:
withArrayClaim(String Name, String[] items)
And there's my first problem: I'm not sure how to properly add this.
Then, is this fragments, which as far as I understand is place where token is verified:
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
String token = request.getHeader(HEADER_STRING);
if (token != null) {
// parse the token.
String user = JWT.require(Algorithm.HMAC512(SECRET.getBytes()))
.build()
.verify(token.replace(TOKEN_PREFIX, ""))
.getSubject();
if (user != null) {
return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
}
return null;
}
return null;
}
What's bother me is fragment:
return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
I don't understand why there is null (Inteliij highlights it as 'credentials') and this new ArrayList. Is there a place, where I should fetch roles from token, and add them?
I know, it's kinda broad scope question, but i couldn't find other solutions.
Or mayby there is an easier way to create simple JWT token authenitcation/authorization (role based).
Looking forward for your answers!
EDIT:
Or mayby is there more simple solutioni - not keeping user roles inside key - but only adding them in this 'second' part where null and new ArrayList is?
Just create the granted authorities based in the user roles and authenticate the user with it. Then the authenticated user principal will contain the roles.
Simple example:
UserEntity userEntity = userRepository.findUserByEmail(user); // this depends of course on your implementation
if (userEntity == null) return null;
List<RoleEntity> roles = userEntity.getRoles();
Collection<GrantedAuthority> authorities = new HashSet<>();
roles.forEach((role) -> {
authorities.add(new SimpleGrantedAuthority(role.getName()));
});
return new UsernamePasswordAuthenticationToken(user, null, authorities);
Even better, you can create a UserPrincipal that implements UserDetails from spring security.
public class UserPrincipal implements UserDetails {
private static final long serialVersionUID = 1L;
private final UserEntity userEntity;
public UserPrincipal(UserEntity userEntity){
this.userEntity = userEntity;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new HashSet<>();
// Get user Roles
Collection<RoleEntity> roles = userEntity.getRoles();
if(roles == null) return authorities;
roles.forEach((role) -> {
authorities.add(new SimpleGrantedAuthority(role.getName()));
});
return authorities;
}
#Override
public String getPassword() {
return this.userEntity.getEncryptedPassword();
}
#Override
public String getUsername() {
return this.userEntity.getEmail();
}
#Override
public boolean isAccountNonExpired() {
return false;
}
#Override
public boolean isAccountNonLocked() {
return false;
}
#Override
public boolean isCredentialsNonExpired() {
return false;
}
#Override
public boolean isEnabled() {
return false;
}
}
And to use it:
UserEntity userEntity = userRepository.findUserByEmail(user);
if (userEntity == null) return null;
UserPrincipal userPrincipal = new UserPrincipal(userEntity);
return new UsernamePasswordAuthenticationToken(userPrincipal, null, userPrincipal.getAuthorities());
I have a project for which I had a custom implementation of a JWT token and I could get an access token from an integration test with the call to the method:
private void addTokenToRequestHeader(HttpHeaders headers, String username) {
tokenAuthenticationService.addAccessTokenToHeader(headers, username);
}
Now I'm changing the security to use OAuth2 and my configuration is not anymore using the custom JWT implementation:
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
// The client id and client secret
.withClient(OAUTH_CLIENT_ID)
.secret(OAUTH_CLIENT_SECRET)
// The endpoint at the client application to redirect to
.redirectUris(OAUTH_CLIENT_URL)
// The type of request the authorization server expects for the client
.authorizedGrantTypes(OAUTH_GRANT_TYPE_PASSWORD, OAUTH_GRANT_TYPE_AUTHORIZATION_CODE, OAUTH_GRANT_TYPE_REFRESH_TOKEN)
// The permissions the client needs to send requests to the authorization server
.authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
// The resources server id
.resourceIds(RESOURCE_SERVER_ID)
// The scope of content offered by the resources servers
.scopes("read_profile", "write_profile", "read_firstname")
// The lifespan of the tokens for the client application
.accessTokenValiditySeconds(jwtProperties.getAccessTokenExpirationTime())
.refreshTokenValiditySeconds(jwtProperties.getRefreshTokenExpirationTime());
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
.passwordEncoder(oauthClientPasswordEncoder);
}
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)
.tokenServices(defaultTokenServices())
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
.tokenEnhancer(jwtAccessTokenConverter())
.accessTokenConverter(jwtAccessTokenConverter())
.userDetailsService(userDetailsService);
}
#Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setKeyPair(new KeyStoreKeyFactory(new ClassPathResource(jwtProperties.getSslKeystoreFilename()), jwtProperties.getSslKeystorePassword().toCharArray()).getKeyPair(jwtProperties.getSslKeyPair()));
return jwtAccessTokenConverter;
}
#Bean
public TokenEnhancer tokenEnhancer() {
return new CustomTokenEnhancer();
}
// Add user information to the token
class CustomTokenEnhancer implements TokenEnhancer {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
User user = (User) authentication.getPrincipal();
Map<String, Object> info = new LinkedHashMap<String, Object>(accessToken.getAdditionalInformation());
info.put(CommonConstants.JWT_CLAIM_USER_EMAIL, user.getEmail().getEmailAddress());
info.put(CommonConstants.JWT_CLAIM_USER_FULLNAME, user.getFirstname() + " " + user.getLastname());
info.put("scopes", authentication.getAuthorities().stream().map(s -> s.toString()).collect(Collectors.toList()));
info.put("organization", authentication.getName());
DefaultOAuth2AccessToken customAccessToken = new DefaultOAuth2AccessToken(accessToken);
customAccessToken.setAdditionalInformation(info);
customAccessToken.setExpiration(tokenAuthenticationService.getExpirationDate());
return customAccessToken;
}
}
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
#Bean
#Primary
public DefaultTokenServices defaultTokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
return defaultTokenServices;
}
And its accompanying security configuration:
#Bean
public UserDetailsService userDetailsService() {
return new UserDetailsServiceImpl();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(userPasswordEncoder);
}
// Allow preflight requests
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
}
Now, to get the access token, I have to send a request:
#Before
public void setup() throws Exception {
super.setup();
addTokenToRequestHeader(httpHeaders, ClientFixtureService.CLIENT_ID, UserFixtureService.USER_EMAIL, UserFixtureService.USER_PASSWORD);
}
private void addTokenToRequestHeader(HttpHeaders headers, String oauthClientId, String username, String password) throws Exception {
String token = getOAuthAccessToken(oauthClientId, username, password);
headers.remove(CommonConstants.ACCESS_TOKEN_HEADER_NAME);
headers.add(CommonConstants.ACCESS_TOKEN_HEADER_NAME, tokenAuthenticationService.buildOAuthAccessToken(token));
}
private void addBase64UserPasswordHeaders(String username, String password, HttpHeaders httpHeaders) {
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
httpHeaders.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
String usernamePassword = username + ":" + password;
String encodedAuthorisation = Base64.getEncoder().encodeToString(usernamePassword.getBytes(UTF_8));
httpHeaders.add(CommonConstants.ACCESS_TOKEN_HEADER_NAME,
CommonConstants.AUTH_BASIC + " " + new String(encodedAuthorisation));
}
private String getOAuthAccessToken(String oauthClientId, String username, String password) throws Exception {
MultiValueMap<String, String> oauthParams = new LinkedMultiValueMap<>();
oauthParams.add("grant_type", AuthorizationServerConfiguration.OAUTH_GRANT_TYPE_PASSWORD);
oauthParams.add("client_id", oauthClientId);
oauthParams.add("username", username);
oauthParams.add("password", password);
addBase64UserPasswordHeaders(AuthorizationServerConfiguration.OAUTH_CLIENT_ID, AuthorizationServerConfiguration.OAUTH_CLIENT_SECRET, httpHeaders);
ResultActions mvcResult = this.mockMvc
.perform(post(RESTConstants.SLASH + DomainConstants.AUTH + RESTConstants.SLASH + DomainConstants.TOKEN)
.headers(httpHeaders)
.params(oauthParams)
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isOk());
String resultString = mvcResult.andReturn().getResponse().getContentAsString();
JacksonJsonParser jsonParser = new JacksonJsonParser();
return jsonParser.parseMap(resultString).get("access_token").toString();
}
But when running the debugger, my user details implementation loadUserByUsername method is never called up. It was called up before when I had a custom implementation of a JWT token and no OAuth2 configuration:
#Service
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
private CredentialsService credentialsService;
#Override
#Transactional
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if (username != null && !username.isEmpty()) {
try {
User user = credentialsService.findByEmail(new EmailAddress(username));
return new UserDetailsWrapper(user);
} catch (EntityNotFoundException e) {
throw new UsernameNotFoundException("The user " + username + " was not found.");
}
}
throw new UsernameNotFoundException("The user " + username + " was not found.");
}
}
My request looks like:
MockHttpServletRequest:
HTTP Method = POST
Request URI = /auth/token
Parameters = {grant_type=[password], client_id=[ng-xxx], username=[xxx#yahoo.xx], password=[xxxx]}
Headers = {Content-Type=[application/json, application/json], Accept=[application/json], Authorization=[Basic bmctemxxbzpzZWNyZXQ=]}
Body = <no character encoding set>
Here is what the console log has to say:
2019-01-08 09:08:11.841 DEBUG 18338 --- [ main] o.s.s.w.a.www.BasicAuthenticationFilter : Authentication request for failed: org.springframework.security.authentication.BadCredentialsException: Bad credentials
2019-01-08 09:08:11.842 DEBUG 18338 --- [ main] s.w.a.DelegatingAuthenticationEntryPoint : Trying to match using RequestHeaderRequestMatcher [expectedHeaderName=X-Requested-With, expectedHeaderValue=XMLHttpRequest]
2019-01-08 09:08:11.842 DEBUG 18338 --- [ main] s.w.a.DelegatingAuthenticationEntryPoint : No match found. Using default entry point org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint#6cb84986
You get the following Exception when you try to POST to /oauth/token with Authorization header with Basic credentials. This with the fact that your UserDetailsService.loadUserByUsername method never gets called means that your OAuth2 Client Credentials ng-zlqo:secret are not registered in the OAuth2 AuthorizationServer.
o.s.s.w.a.www.BasicAuthenticationFilter : Authentication request for failed: org.springframework.security.authentication.BadCredentialsException: Bad credentials