I'm working in a project apply Spring boot and JWT.
In OAuth2 configuration, I added more information into JWT sucessfully but I don't know how to extract this information when process a request contained my information.
Below is the code segment with I added my additional information:
public class CustomTokenEnhancer implements TokenEnhancer {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
Map<String, Object> additionalInfo = new HashMap<>();
additionalInfo.put("user_name", authentication.getName());
User user = userService().getUserDetailsByLoginId(authentication.getName());
additionalInfo.put("user_id", user.getRelationPartId());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
}
}
If you had experienced on it, please help me to get user_id from my token when process a request.
Thanks
Finally, I got a solution, it works like a champ...
Below is some code segment, hope it help...
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Bean
public TokenEnhancer customTokenEnhancer() {
return new CustomTokenEnhancer();
}
#Bean
public DefaultAccessTokenConverter customAccessTokenConverter() {
return new DefaultAccessTokenConverter();
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(customTokenEnhancer(), accessTokenConverter()));
endpoints.tokenStore(tokenStore()).tokenEnhancer(tokenEnhancerChain)
.accessTokenConverter(customAccessTokenConverter())
.authenticationManager(authenticationManager);
}
In Controller:
#Autowired
private TokenStore tokenStore;
#ApiOperation(value = "test get security data", response = String.class)
#RequestMapping(value = "/getUser1", method = RequestMethod.GET)
public #ResponseBody String getData1(OAuth2Authentication principal) {
OAuth2AuthenticationDetails auth2AuthenticationDetails = (OAuth2AuthenticationDetails) principal.getDetails();
Map<String, Object> details = tokenStore.readAccessToken(auth2AuthenticationDetails.getTokenValue()).getAdditionalInformation();
String department= (String) details.get("department");
return null;
}
Related
I'm new in Java/Spring boot and I'm doing a login with jwt.
What happens is that I got well to the part of creating users, the issue is that when I want to log in postman I get "401 bad credentials" and in the netbeans console I get Encoded password does not look like Bcrypt and I fail the commence method.
I read my code like crazy and still can't figure out what's wrong with it.
Authcontroller.java
java
#RestController
#RequestMapping("/auth")
#CrossOrigin
public class AuthController {
#Autowired
PasswordEncoder passwordEncoder;
#Autowired
AuthenticationManager authenticationManager;
#Autowired
UserService userService;
#Autowired
RolService rolService;
#Autowired
JwtProvider jwtProvider;
#PostMapping("/new")
public ResponseEntity<?> nuevo(#Valid #RequestBody NewUser newUser, BindingResult bindingResult){
if(bindingResult.hasErrors())
return new ResponseEntity(new Msg("Campos o email inválido"), HttpStatus.BAD_REQUEST);
if(userService.existsByUsername(newUser.getUsername))
return new ResponseEntity(new Msg("Nombre de usuario existente"), HttpStatus.BAD_REQUEST);
if(userService.existsByEmail(newUser.getEmail))
return new ResponseEntity(new Msg("Email existente"), HttpStatus.BAD_REQUEST);
User user = new User(newUser.getName(), newUser.getUsername(), newUser.getEmail(), passwordEncoder.encode(newUser.getPassword()));
Set<Rol> roles = new HashSet<>();
roles.add(rolService.getByRolName(RolName.ROLE_USER).get());
if(newUser.getRoles().contains("admin"))
roles.add(rolService.getByRolName(RolName.ROLE_ADMIN).get());
user.setRoles(roles);
userService.save(user);
return new ResponseEntity(new Msg("usuario guardado"), HttpStatus.CREATED);
}
#PostMapping("/login")
public ResponseEntity<JwtDto> login (#Valid #RequestBody UserLogin userLogin, BindingResult bindingResult){
if (bindingResult.hasErrors())
return new ResponseEntity(new Msg("Campos mal ingresados"), HttpStatus.BAD_REQUEST);
Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
userLogin.getUsername(), userLogin.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = jwtProvider.generateToken(authentication);
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
JwtDto jwtDto = new JwtDto(jwt, userDetails.getUsername(), userDetails.getAuthorities());
return new ResponseEntity(jwtDto, HttpStatus.OK);
}
}
MainSecurity.java
java
#Configuration
#EnableWebSecurity
#EnableMethodSecurity
public class MainSecurity {
#Autowired
UserDetailsServiceImpl userDetailsServiceImpl;
#Autowired
JwtEntryPoint jwtEntryPoint;
#Bean
public JwtTokenFilter jwtTokenFilter() {
return new JwtTokenFilter();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration)
throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.exceptionHandling().authenticationEntryPoint(jwtEntryPoint).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeHttpRequests()
.requestMatchers("/**").permitAll()
.anyRequest().authenticated();
http.addFilterBefore(jwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("*");
}
};
}
}
It seems that you are not actually storing bcrypt encoded passwords in your database.
However, the system is expecting them. Either that or the password hashes are invalid. When you register a new user, you should make sure the password is hashed using bcrypt before you store the user.
Try checking your database manually first, by looking into the UserEntity you may have created on the corresponding table.
If the password is not being encrypted, make sure it is being used by a passwordEncoder() method which should in turn be using a BCryptPasswordEncoder object to actually encode your input password.
Hope it helped!!
I am facing problem related to Session ID. I am using Spring Boot + Mongo DB Authentication.
Below is code and Configuration set:
application.properties
spring.session.store-type=mongodb
SecurityConfig.java
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter{
#Autowired
private PasswordEncoder passwordEncoder;
#Bean
public UserDetailsService mongoUserDetails() {
return new UserDetailsServiceImpl();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
UserDetailsService userDetailsService = mongoUserDetails();
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/auth/login", "/api/auth/signup")
.permitAll().anyRequest()
.authenticated();
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
PasswordConfig.java
#Configuration
public class PasswordConfig {
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(10);
}
}
HttpSessionConfig.java
//This is to enable Mongo Http Session
#EnableMongoHttpSession(maxInactiveIntervalInSeconds = 5*60)
#Configuration
public class HttpSessionConfig {
#Bean
public JdkMongoSessionConverter jdkMongoSessionConverter() {
//This duration does not work for setting the Inactive Time for Mongo Session
return new JdkMongoSessionConverter(Duration.ofMinutes(30));
}
}
UserDetailsServiceImpl.java
#Service
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if (user != null) {
List<GrantedAuthority> authorities = getUserAuthority(user.getRoles());
return buildUserForAuthentication(user, authorities);
} else {
throw new UsernameNotFoundException("username not found");
}
}
private UserDetails buildUserForAuthentication(User user, List<GrantedAuthority> authorities)
{
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), authorities);
}
private List<GrantedAuthority> getUserAuthority(Set<Role> userRoles) {
Set<GrantedAuthority> roles = new HashSet<>();
userRoles.forEach((role) -> {
for (GrantedAuthority grantedAuthority : role.getRole().getGrantedAuthorities()) {
roles.add(grantedAuthority);
}
});
List<GrantedAuthority> grantedAuthorities = new ArrayList<>(roles);
return grantedAuthorities;
}
}
AuthController.java
#RestController
#RequestMapping("/api/auth")
public class AuthController {
private final static int IDX_USERNAME = 0;
private final static int IDX_PWD = 1;
#Autowired
private UserService userService;
#Autowired
private AuthenticationManager authenticationManager;
#PostMapping("/login")
public ResponseEntity<?> authenticateUser(HttpServletRequest req,
#RequestHeader("Authorization") String encUser) {
byte[] decoded = Base64.getDecoder().decode(encUser);
String decodedStr = new String(decoded, StandardCharsets.UTF_8);
String[] userCred = decodedStr.split(":");
String username = userCred[IDX_USERNAME];
String pwd = userCred[IDX_PWD];
Authentication authentication;
try {
authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, pwd));
} catch (BadCredentialsException e) {
return new ResponseEntity(e.getMessage(), HttpStatus.FORBIDDEN);
}
SecurityContext sc = SecurityContextHolder.getContext();
sc.setAuthentication(authentication);
HttpSession session = req.getSession(true);
session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, sc);
return new ResponseEntity(HttpStatus.NO_CONTENT);
}
#PostMapping("/logout")
public ResponseEntity<?> logoutUser(HttpServletRequest request, HttpSession session) {
// SecurityContextHolder.getContext().setAuthentication(null);
// SecurityContextHolder.clearContext();
if (session != null) {
session.invalidate();
}
Cookie[] cookies = request.getCookies();
if (cookies != null && cookies.length > 0) {
for (int i = 0; i < cookies.length; i++) {
cookies[i].setMaxAge(0);
}
}
return new ResponseEntity(HttpStatus.NO_CONTENT);
}
}
EmployeeController
#RestController
#RequestMapping("/employee")
public class EmployeeController {
#Autowired
private EmployeeService service;
#GetMapping("/leaves")
#PreAuthorize("hasAnyRole('ROLE_USER')")
public ResponseEntity showLeaves(#RequestParam String user) {
System.out.println("User: " + user);
Employee emp = service.findLeaves(user);
if (emp == null) {
return new ResponseEntity(HttpStatus.NOT_FOUND);
}
return new ResponseEntity(emp, HttpStatus.OK);
}
}
I Login using /api/auth/login API(AuthController.java) using manual authentication(not using httpBasic) by passing base64 ecnryption of username:password in Authorization Header. Authentication works using mongoDB user table and Spring will return the SESSION in the "set-cookie"
Concern 1
Till this point, its ok. Now when i send the request to /employee/leaves (EmployeeController.java) by sending SESSION received from login response in the Authorization Header, spring boot always returns "Forbidden" even though SESSION object is present in the DB before it does land in to the api
Concern 2
If i send login on the already login user, spring boot generates new session id which is also wrong. I tried using SessionManagement().setMaxSession(1) but did not work. I feel this is happening because of using manual authentication. Just a guess.
NOTE: I want to support login for different users(may be not from the same machine).
One Wierd behavior observed that once in while /employee/leaves works. I don't know how it happened.
I seacrhed a lot and spent around 2-3 days but did not get anything.
Any guidance will be great.
Thanks a lot in Advance
I am signing JWT with private key (authorization server) and I am using public key (resource server) to "verify" it...
How can I know whether the JWT has not been compromised? Or how can I do that?
The code is from resource server
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
Resource resource = new ClassPathResource("public.txt");
String publicKey = null;
try {
publicKey = IOUtils.toString(resource.getInputStream());
} catch (final IOException e) {
throw new RuntimeException(e);
}
converter.setVerifierKey(publicKey);
return converter;
}
Spring Security will do the verification of the token based on configurations in authorization server.
For a standalone verification, the code would be like:
RsaVerifier verifier = new RsaVerifier(RSAPublicKey);
Jwt tokenDecoded = JwtHelper.decodeAndVerify(token, verifier);
Map<String, Object> claimsMap = (Map<String, Object>) new
ObjectMapper().readValue(tokenDecoded.getClaims(), Map.class);
//Verify the claims then
// 1 Verify if the token has not already expired
// 2 Verify the issuance date ( should be before this date )
// 3 Verify if the issuer of this token is contained in verified authorities.
// 4 Verify if the token was issued for this client
// 5 Verify if the token contained any expected claims...
But the above is implemented by Spring Security for Oauth2 authentication process, client application just needs to provide configurations.
The trigger is OAuth2AuthenticationProcessingFilter in the Spring security filter chain. This filter is added when resources are protected by Oauth2 security.
In your application, the authorization server configuration would look like ( only relevant indicative configuration extracts below)
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
...
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
RSAPemKeyPairLoader keyPairLoader = new RSAPemKeyPairLoader();
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(...);
converter.setVerifierKey(...);
return converter;
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(...);
defaultTokenServices.setAccessTokenValiditySeconds(...);
defaultTokenServices.setRefreshTokenValiditySeconds(...);
return defaultTokenServices;
}
}
In your application, the Resource Server configuration would be like:
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
...
}
}
To trace in Spring implementation where the requested token is intercepted and verified look at the Spring OAUTH2 implementation - flow details below, where Authentication object, an instance of OAuth2Authentication would be attempted to be created for successful requests.
All below Code extracts are from spring-security-oauth2-2.0.8.RELEASE implementations.
public class OAuth2AuthenticationManager implements AuthenticationManager {
....
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (authentication == null) {
throw new InvalidTokenException("Invalid token (token not found)");
}
String token = (String) authentication.getPrincipal();
OAuth2Authentication auth = tokenServices.loadAuthentication(token);
...
}
}
The loadAuthentication would be basically verifying the access token and attempting to convert it into OAuth2Authentication
public class DefaultTokenServices implements AuthorizationServerTokenServices ...{
public OAuth2Authentication loadAuthentication(String accessTokenValue) throws AuthenticationException, InvalidTokenException {
OAuth2AccessToken accessToken = tokenStore.readAccessToken(accessTokenValue);
...
}
}
JwtTokenStore would create OAuth2AccessToken and in the process decode and verify the String token.
public class JwtTokenStore implements TokenStore {
public OAuth2AccessToken readAccessToken(String tokenValue) {
OAuth2AccessToken accessToken = convertAccessToken(tokenValue);
if (jwtTokenEnhancer.isRefreshToken(accessToken)) {
throw new InvalidTokenException("Encoded token is a refresh token");
}
return accessToken;
}
private OAuth2AccessToken convertAccessToken(String tokenValue) {
return jwtTokenEnhancer.extractAccessToken(tokenValue, jwtTokenEnhancer.decode(tokenValue));
}
}
JWTAccessTokenConverter does the decoding and extraction of token claims.
public class JwtAccessTokenConverter implements AccessTokenConverter {
protected Map<String, Object> decode(String token) {
try {
Jwt jwt = JwtHelper.decodeAndVerify(token, verifier);
String content = jwt.getClaims();
Map<String, Object> map = objectMapper.parseMap(content);
if (map.containsKey(EXP) && map.get(EXP) instanceof Integer) {
Integer intValue = (Integer) map.get(EXP);
map.put(EXP, new Long(intValue));
}
return map;
}
catch (Exception e) {
throw new InvalidTokenException("Cannot convert access token to JSON", e);
}
}
JwtHelper would do the decoding and request verification.
public static Jwt decodeAndVerify(String token, SignatureVerifier verifier) {
Jwt jwt = decode(token);
jwt.verifySignature(verifier);
return jwt;
}
JwttImpl invokes the verifier.
public void verifySignature(SignatureVerifier verifier) {
verifier.verify(signingInput(), crypto);
}
For example, RSA Signature verifier would finally do the verification:
public class RsaVerifier implements SignatureVerifier {
public void verify(byte[] content, byte[] sig) {
try {
Signature signature = Signature.getInstance(algorithm);
signature.initVerify(key);
signature.update(content);
if (!signature.verify(sig)) {
throw new InvalidSignatureException("RSA Signature did not match content");
}
}
catch (GeneralSecurityException e) {
throw new RuntimeException(e);
}
}
}
When to access any route, I get redirected to my login page, including accessing my login page, which is expected. Now the problem is the login page does not even load. I have configured the controller method, hander, template configuration, and security configuration necessary to make this work, but all I get is the error below:
http://localhost:8080/login?errorMessage=Login+to+use+the+app
UserController.java
#Controller
public class UserController {
#Autowired
private UserService userService;
#GetMapping("/signup")
public String signup(Model model) {
model.addAttribute("user", new User());
return "signup";
}
#PostMapping("/users")
public String createUser(User user) {
// only create user if it does not exist
if (userService.findByUsername(user.getUsername()) == null) {
user.setRoles(new String[] {"ROLE_USER"});
userService.save(user);
return "redirect:/login";
}
else {
return "redirect:/signup";
}
}
#GetMapping("/login")
public String login(Model model) {
model.addAttribute("user", new User());
return "login";
}
#GetMapping("/profile")
public String currentUserProfile(Model model) {
User currentUser = (User) model.asMap().get("currentUser");
model.addAttribute("user", currentUser);
model.addAttribute("authorized", true);
return "profile";
}
#GetMapping("/users/{id}")
public String userProfile(#PathVariable Long id, Model model) {
User queriedUser = userService.findOne(id);
model.addAttribute("user", queriedUser);
User currentUser = (User) model.asMap().get("currentUser");
if (currentUser != null && currentUser.isAdmin()) {
model.addAttribute("authorized", true);
}
return "profile";
}
}
UserHandler.java
#ControllerAdvice(basePackages = "com.valencra.recipes.web.controller")
public class UserHandler {
public static final String USERNAME_NOT_FOUND_ERR_MSG = "Unable to find username";
public static final String ACCESS_DENIED_ERR_MSG = "Login to use the app";
#Autowired
private UserService userService;
#ModelAttribute("authenticatedUser")
public User addAuthenticatedUser() {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null) {
String username = authentication.getName();
User user = userService.findByUsername(username);
if (user != null) {
return user;
}
else {
throw new UsernameNotFoundException(USERNAME_NOT_FOUND_ERR_MSG);
}
}
else {
throw new AccessDeniedException(ACCESS_DENIED_ERR_MSG);
}
}
#ExceptionHandler(AccessDeniedException.class)
public String redirectIfUserNotAuthenticated(RedirectAttributes redirectAttributes) {
redirectAttributes.addAttribute("errorMessage", ACCESS_DENIED_ERR_MSG);
return "redirect:/login";
}
#ExceptionHandler(UsernameNotFoundException.class)
public String redirectIfUserNotFound(RedirectAttributes redirectAttributes) {
redirectAttributes.addAttribute("errorMessage", USERNAME_NOT_FOUND_ERR_MSG);
return "redirect:/login";
}
}
TemplateConfig.java
#Configuration
public class TemplateConfig {
#Bean
public SpringResourceTemplateResolver templateResolver() {
final SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setPrefix("classpath:/templates/");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode("LEGACYHTML5");
return templateResolver;
}
#Bean
public SpringTemplateEngine templateEngine() {
final SpringTemplateEngine springTemplateEngine = new SpringTemplateEngine();
springTemplateEngine.addTemplateResolver(templateResolver());
springTemplateEngine.addDialect(new SpringSecurityDialect());
return springTemplateEngine;
}
#Bean
public ThymeleafViewResolver viewResolver() {
final ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine());
viewResolver.setOrder(1);
return viewResolver;
}
}
SecurityConfig.java
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private RecipesAppUserDetailsService recipesAppUserDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(recipesAppUserDetailsService)
.passwordEncoder(User.PASSWORD_ENCODER);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/signup").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.successHandler(loginSuccessHandler())
.failureHandler(loginFailureHandler())
.and()
.logout()
.permitAll()
.logoutSuccessUrl("/login")
.and()
.csrf().disable();
http.headers().frameOptions().disable();
}
public AuthenticationSuccessHandler loginSuccessHandler() {
return (request, response, authentication) -> response.sendRedirect("/");
}
public AuthenticationFailureHandler loginFailureHandler() {
return (request, response, exception) ->
response.sendRedirect("/login");
}
#Bean
public EvaluationContextExtension securityExtension() {
return new EvaluationContextExtensionSupport() {
#Override
public String getExtensionId() {
return "security";
}
#Override
public Object getRootObject() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return new SecurityExpressionRoot(authentication) {};
}
};
}
}
application.properties
# Package where our entities (models) are located
recipes.entity.package = com.valencra.recipes.model
# Details for our datasource
recipes.db.driver = org.h2.Driver
recipes.db.url = jdbc:h2:mem:recipes
# Hibernate properties
hibernate.dialect = org.hibernate.dialect.H2Dialect
hibernate.implicit_naming_strategy = org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl
hibernate.format_sql = true
hibernate.show_sql = true
hibernate.hbm2ddl.auto = create-drop
spring.data.rest.basePath=/api/v1
resources directory
EDIT: See #lgaleazzi's answer, and the comments that follow. Essentially, removing the UserHandler class fixes it.
What does the stack trace say?
Looking at your UserHandler, you handle a null authenticated object, and a user you can find. You don't deal with what happens with an anonymous user. I think that's the issue.
If nobody is authenticated, you'll get an authentication object with an anonymous user. You can check that with the method isAuthenticated(). But you don't actually have to write this code, Spring Boot handles all this pretty well with its default configuration.
Try adding the following method to SecurityConfig:
#Override
public void configure(WebSecurity web) throws Exception {
// configuring here URLs for which security filters
// will be disabled (this is equivalent to using
// security="none")
web.ignoring().antMatchers("/login");
}
There you can specify URLs in your application for which authentication should not be applied (also useful for static resources).
In your case, /login is not excluded from authentication scope, so it causes one more redirection to /login, and you get a vicious circle.
I am building microservice applications with spring cloud, oauth and JWT. My Oauth2 server generates JWT token but when I am trying to validate the token in gateway (implemented using ZUUL) I am getting below Error
Could you please let me know what is wrong and what could be the solution.
I am using Spring 4.3, Spring boot 1.5.8, Spring cloud Dalston.SR4
org.springframework.security.jwt.crypto.sign.InvalidSignatureException: Calculated signature did not match actual value
at org.springframework.security.jwt.crypto.sign.MacSigner.verify(MacSigner.java:62) ~[spring-security-jwt-1.0.8.RELEASE.jar:na]
at org.springframework.security.jwt.JwtImpl.verifySignature(JwtHelper.java:287) ~[spring-security-jwt-1.0.8.RELEASE.jar:na]
at org.springframework.security.jwt.JwtHelper.decodeAndVerify(JwtHelper.java:77) ~[spring-security-jwt-1.0.8.RELEASE.jar:na]
at com.debopam.gateway.filter.CustomPostZuulFilter.run(CustomPostZuulFilter.java:57) ~[classes/:na]
at com.netflix.zuul.ZuulFilter.runFilter(ZuulFilter.java:112) [zuul-core-1.3.0.jar:1.3.0]
at com.netflix.zuul.FilterProcessor.processZuulFilter(FilterProcessor.java:193) [zuul-core-1.3.0.jar:1.3.0]
at com.netflix.zuul.FilterProcessor.runFilters(FilterProcessor.java:157) [zuul-core-1.3.0.jar:1.3.0]
at com.netflix.zuul.FilterProcessor.postRoute(FilterProcessor.java:92) [zuul-core-1.3.0.jar:1.3.0]
at com.netflix.zuul.ZuulRunner.postRoute(ZuulRunner.java:87) [zuul-core-1.3.0.jar:1.3.0]
I have used signing key 12345AsDfG in both Auth server and gateway server.
Below Are the code snippet
Auth Server
#Configuration
public class JWTTokenStoreConfig {
#Autowired
private ServiceConfig serviceConfig;
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
defaultTokenServices.setAccessTokenValiditySeconds(60*30);
return defaultTokenServices;
}
#Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(serviceConfig.getJwtSigningKey());
return converter;
}
#Bean
public TokenEnhancer jwtTokenEnhancer() {
return new JWTTokenEnhancer();
}
}
#Configuration
public class JWTOAuth2Config extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private TokenStore tokenStore;
#Autowired
private DefaultTokenServices tokenServices;
#Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
#Autowired
private TokenEnhancer jwtTokenEnhancer;
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception
{
//oauthServer.checkTokenAccess("permitAll()");
oauthServer
.tokenKeyAccess("isAnonymous() || hasAuthority('ROLE_TRUSTED_CLIENT')")
.checkTokenAccess("permitAll()");
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtTokenEnhancer, jwtAccessTokenConverter));
endpoints.tokenStore(tokenStore) //JWT
.accessTokenConverter(jwtAccessTokenConverter) //JWT
.tokenEnhancer(tokenEnhancerChain) //JWT
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("uiapp")
.secret("secret")
.authorizedGrantTypes("refresh_token", "password", "client_credentials")
.scopes("webclient", "mobileclient");
}
}
In the Gateway application, I am using below code to verify the Token
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
try {
InputStream is = ctx.getResponseDataStream();
String responseBody = IOUtils.toString(is);
if (StringUtils.hasText(responseBody)
&& responseBody.contains("access_token")) {
Map<String, Object> responseMap = objectMapper.readValue(
responseBody, new TypeReference<Map<String, Object>>() {});
String accesToken = responseMap.get("access_token").toString();
Jwt jwt = JwtHelper.decodeAndVerify(accesToken, new MacSigner(serviceConfig.getJwtSigningKey()));
System.out.println(jwt.getClaims());
//System.out.println(jwt.getBody());
}
ctx.setResponseBody(responseBody);
} catch (Exception e) {
logger.error("Error occured in zuul post filter", e);
}
return null;
}
There was a signing key mismatch between the services.