I am new to Springboot and im trying to filter requests through a Zuul API gateway however i get the error below :
AnonymousAuthenticationToken cannot be cast to org.aacctt.ms.auth.security.JWTAuthentication
When i put a breakpoint i get a null header/token string value when the request reaches the authentication service from zuul gateway, this happens for protected requests that require an authorization token.
My aim is to be able to verify the token sent by clients so that i can allow the client's request to protected endpoints or reject it.
Im not sure what im doing wrong here is my code:
Auth Service
#Component
public class JWTAuthorizationFilter extends GenericFilterBean {
private static final Logger LOG = LoggerFactory.getLogger(JWTAuthorizationFilter.class);
private static final String HEADER_STRING = "Authorization";
public static final String TOKEN_PREFIX = "Bearer ";
#Value("${jwt.encryption.secret}")
private String SECRET;
#Value("${jwt.access.token.expiration.seconds}")
private long EXPIRATION_TIME_IN_SECONDS;
public String generateAccessToken(long userId) {
return JWT.create()
.withSubject(String.valueOf(userId))
.withIssuedAt(new Date())
.withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME_IN_SECONDS * 1000))
.sign(Algorithm.HMAC256(SECRET.getBytes()));
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String header = httpRequest.getHeader(HEADER_STRING); // this is null
if (header == null || !header.startsWith(TOKEN_PREFIX)) {
chain.doFilter(httpRequest, httpResponse);
return;
}
SecurityContextHolder.getContext().setAuthentication(getAuthentication(header));
chain.doFilter(httpRequest, httpResponse);
}
private Authentication getAuthentication(String token) {
final String username;
try {
DecodedJWT jwt = JWT.require(Algorithm.HMAC256(SECRET.getBytes()))
.build()
.verify(token.replace(TOKEN_PREFIX, ""));
username = jwt.getSubject();
} catch (JWTVerificationException e) {
LOG.debug("Invalid JWT", e);
return null;
}
final Long userId;
try {
userId = Long.valueOf(username);
} catch (NumberFormatException e) {
LOG.debug("Invalid JWT. Username is not an user ID");
return null;
}
LOG.debug("Valid JWT. User ID: " + userId);
return new JWTAuthentication(userId);
}
}
WebSecurityConfig
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final JWTAuthorizationFilter jwtAuthorizationFilter;
public WebSecurityConfig(JWTAuthorizationFilter jwtAuthorizationFilter) {
this.jwtAuthorizationFilter = jwtAuthorizationFilter;
}
#Bean
public AuthenticationEntryPoint authenticationEntryPoint() {
return (request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().disable();
http.csrf().disable();
http.addFilterAfter(jwtAuthorizationFilter, BasicAuthenticationFilter.class);
http.authorizeRequests()
.antMatchers("/**").permitAll()
.antMatchers(AccountController.PATH_POST_SIGN_UP).permitAll()
.antMatchers(AccountController.PATH_POST_REFRESH).permitAll()
.antMatchers(AccountController.PATH_POST_LOGIN).permitAll()
.antMatchers("/v2/api-docs",
"/swagger-resources/configuration/ui",
"/swagger-resources",
"/swagger-resources/configuration/security",
"/swagger-ui.html",
"/webjars/**").permitAll()
.anyRequest().authenticated()
;
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
JWTAuthentication
public class JWTAuthentication implements Authentication {
private final long userId;
public JWTAuthentication(long userId) {
this.userId = userId;
}
#Override public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.emptySet();
}
#Override public Object getCredentials() {
return null;
}
#Override public Object getDetails() {
return null;
}
public long getUserId() {
return userId;
}
#Override public Long getPrincipal() {
return userId;
}
#Override public boolean isAuthenticated() {
return true;
}
#Override public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
throw new UnsupportedOperationException("JWT authentication is always authenticated");
}
#Override public String getName() {
return String.valueOf(userId);
}
}
SecurityService
#Service
public class SecurityService {
public long getLoggedUserId() {
JWTAuthentication authentication = (JWTAuthentication) SecurityContextHolder.getContext().getAuthentication();
return authentication.getUserId();
}
}
Zuul Gateway
public class AuthorizationFilter extends BasicAuthenticationFilter {
private static final Logger LOG = LoggerFactory.getLogger(AuthorizationFilter.class);
private static final String HEADER_STRING = "Authorization";
public static final String TOKEN_PREFIX = "Bearer ";
Environment environment;
public AuthorizationFilter(AuthenticationManager authManager, Environment environment) {
super(authManager);
this.environment = environment;
}
#Override
protected void doFilterInternal(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain) throws IOException, ServletException {
String authorizationHeader = req.getHeader(environment.getProperty("authorization.token.header.name"));
if (authorizationHeader == null || !authorizationHeader.startsWith(environment.getProperty("authorization.token.header.prefix"))) {
chain.doFilter(req, res);
return;
}
UsernamePasswordAuthenticationToken authentication = getAuthentication(req);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(req, res);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest req) {
String token = req.getHeader(HEADER_STRING);
final String username;
try {
DecodedJWT jwt = JWT.require(Algorithm.HMAC256(environment.getProperty("token.secret").getBytes()))
.build()
.verify(token.replace(TOKEN_PREFIX, ""));
username = jwt.getSubject();
} catch (JWTVerificationException e) {
LOG.debug("Invalid JWT", e);
return null;
}
final Long userId;
try {
userId = Long.valueOf(username);
} catch (NumberFormatException e) {
LOG.debug("Invalid JWT. Username is not an user ID");
return null;
}
LOG.debug("Valid JWT. User ID: " + userId);
return new UsernamePasswordAuthenticationToken(userId, null, new ArrayList<>());
}
}
The issue is the sensitive header, Authorization is sensitive header by default in Zuul, you just need to override the sensitive headers.
zuul:
sensitive-headers:
-
By setting this property in Zuul gateway application.yml it route request to auth service with the Authorization header
Reference only:
Auth Service reference
JWT based authentication
https://github.com/nikhilmalavia/SpringBootJWT.git
Related
I have a RestController class with a method that returns duplicate json. Weird thing is when I pass a string to the response object. The Json is okay. However when I pass any other object, be it a list, hashmap etc. I get the json duplicated. Am checking the response in PostMan.
Sample Code sending correct response with string.
#RequestMapping(value = "test", method = RequestMethod.GET)
public ResponseEntity<?> test() {
return ResponseEntity
.ok().
body("My test string");
}
Output
Code sending duplicate Json with HashMap or any Model
#RequestMapping(value = "test", method = RequestMethod.GET)
public ResponseEntity<?> test() {
HashMap<String, Object> fullObject = new HashMap<String, Object>();
return ResponseEntity
.ok().
body(fullObject);
}
The response I get
Here is my security config where I pass a few filters during authentication.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
private final JwtConfig jwtConfig;
private final PasswordEncoder passwordEncoder;
private final ApplicationUserService applicationUserService;
#Autowired
public ApplicationSecurityConfig(PasswordEncoder passwordEncoder, ApplicationUserService applicationUserService, JwtConfig jwtConfig) {
this.applicationUserService = applicationUserService;
this.passwordEncoder = passwordEncoder;
this.jwtConfig = jwtConfig;
}
//JWT AUTHENTICATION
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilter(new JwtUsernameAndPasswordAuthenticationFilter(authenticationManager(),jwtConfig))
.addFilterAfter(new JwtTokenVerifier(jwtConfig), JwtUsernameAndPasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/", "index", "/css/*", "/js/*", "/h2/**","/login", "/swagger-ui/**", "/v3/api-docs/**", "/token/refresh").permitAll()
.antMatchers("/api/**").hasRole(STUDENT.name())
.antMatchers(HttpMethod.DELETE, "/management/api/**").hasAuthority(COURSE_WRITE.getPermission())
.antMatchers(HttpMethod.POST, "/management/api/**").hasAuthority(COURSE_WRITE.getPermission())
.antMatchers(HttpMethod.PUT, "/management/api/**").hasAuthority(COURSE_WRITE.getPermission())
.antMatchers("/management/api/**").hasAnyRole(ADMIN.name(), ADMINTRAINEE.name())
.anyRequest()
.authenticated();
}
#Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setPasswordEncoder(passwordEncoder);
provider.setUserDetailsService(applicationUserService);
return provider;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProvider());
}
}
My JWTTokenVerifier
public class JwtTokenVerifier extends OncePerRequestFilter {
private final JwtConfig jwtConfig;
public JwtTokenVerifier(JwtConfig jwtConfig) {
this.jwtConfig = jwtConfig;
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (request.getServletPath().equals("/login") || request.getServletPath().equals("/token/refresh")) {
filterChain.doFilter(request, response);
}
String authorizationHeader = request.getHeader(jwtConfig.getAuthorizationHeader());
if (Strings.isNullOrEmpty(authorizationHeader) || !authorizationHeader.startsWith(jwtConfig.getTokenPrefix())) {
filterChain.doFilter(request, response);
return;
}
String token = authorizationHeader.replace(jwtConfig.getTokenPrefix(), "");
try {
Algorithm algorithm = Algorithm.HMAC256("secret".getBytes());
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT decodedJWT = verifier.verify(token);
String username = decodedJWT.getSubject();
String[] roles = decodedJWT.getClaim("authorities").asArray(String.class);
Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
stream(roles).forEach(role -> {
authorities.add(new SimpleGrantedAuthority(role));
});
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, null, authorities);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request, response);
} catch (IllegalStateException e) {
throw new IllegalStateException(String.format("Token %s cannot be trusted", token));
}
filterChain.doFilter(request, response);
}
}
My JWTUsernameandpassword
public class JwtUsernameAndPasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
private final JwtConfig jwtConfig;
public JwtUsernameAndPasswordAuthenticationFilter(AuthenticationManager authenticationManager,
JwtConfig jwtConfig) {
this.authenticationManager = authenticationManager;
this.jwtConfig = jwtConfig;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
try {
UsernameAndPasswordAuthenticationRequest authenticationRequest = new ObjectMapper()
.readValue(request.getInputStream(), UsernameAndPasswordAuthenticationRequest.class);
Authentication authentication = new UsernamePasswordAuthenticationToken(
authenticationRequest.getUsername(),
authenticationRequest.getPassword()
);
Authentication authenticate = authenticationManager.authenticate(authentication);
return authenticate;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
/* String access_token = Jwts.builder()
.setSubject(authResult.getName())
.claim("authorities", authResult.getAuthorities())
.setIssuedAt(new Date())
.setExpiration(java.sql.Date.valueOf(LocalDate.now().plusDays(jwtConfig.getTokenExpirationAfterDays())))
.signWith(secretKey)
.compact();
String refresh_token = Jwts.builder()
.setSubject(authResult.getName())
.setIssuedAt(new Date())
.setExpiration(java.sql.Date.valueOf(LocalDate.now().plusDays(jwtConfig.getRefreshTokenExpirationAfterDays())))
.signWith(secretKey)
.compact();*/
Algorithm algorithm = Algorithm.HMAC256("secret".getBytes());
String access_token = JWT.create()
.withSubject(authResult.getName())
.withExpiresAt(new Date(System.currentTimeMillis() + 10 * 6000 * 1000))
.withIssuer(request.getRequestURL().toString())
.withClaim("authorities", authResult.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()))
.sign(algorithm);
String refresh_token = JWT.create()
.withSubject(authResult.getName())
.withExpiresAt(new Date(System.currentTimeMillis() + 30 * 60 * 1000))
.withIssuer(request.getRequestURL().toString())
.sign(algorithm);
// response.addHeader(jwtConfig.getAuthorizationHeader(), jwtConfig.getTokenPrefix() + access_token);
// response.setHeader("jwt_access", jwtConfig.getTokenPrefix() + access_token);
// response.setHeader("jwt_refresh", jwtConfig.getTokenPrefix() + refresh_token);
Map<String, String> tokens = new HashMap<>();
tokens.put("access_token", jwtConfig.getTokenPrefix() + access_token);
tokens.put("refresh_token", refresh_token);
response.setContentType(APPLICATION_JSON_VALUE); //"application/json"
new ObjectMapper().writeValue(response.getOutputStream(), tokens);
}
}
In your JwtTokenVerifier you call filterChain.doFilter(request, response) multiple times. For example, you call it both in the try/catch and outside:
try {
// ...
// Called here
filterChain.doFilter(request, response);
} catch (IllegalStateException e) {
throw new IllegalStateException(String.format("Token %s cannot be trusted", token));
}
// And here
filterChain.doFilter(request, response);
The solution is to refactor your code so that it's only calling the filterChain.doFilter(request, response) statement once.
P.S. You also call filterChain.doFilter(request, response) in the if-statements at the top of your logic. So for the /login or /token/refresh endpoint, the response might be tripled.
I have endpoints that will be called only from authenticated users. I have valid JWT token that I am passing as a Bearer Token in Authorization. When I do get request to http://localhost:8080/api/students/all, I am getting 401:
The logger logs in the console Authenticated user test#gmail.com[ROLE_STUDENT], setting security context, which means that the token is successfully validated. Why am I still gettin 401? How can I solve this issue?
Here is my StudentController.java:
#RestController
#CrossOrigin(origins = "http://localhost:3000")
#Slf4j
#RequestMapping("/api/students")
public class StudentController {
#Autowired
private StudentService studentService;
#GetMapping("/all")
public ResponseEntity<List<Student>> getAllStudents(){
log.info("test");
return new ResponseEntity<List<Student>>(studentService.getAll(), HttpStatus.OK);
}
}
JwtTokenUtil class is:
#Component
public class JwtTokenUtil implements Serializable{
#Value("${jwt.public.key}")
private String publicKey;
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser()
.setSigningKey(publicKey)
.parseClaimsJws(token)
.getBody();
}
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
public String generateToken(User user, String scope) {
return doGenerateToken(user.getEmail(), scope);
}
private String doGenerateToken(String subject, String scope) {
Claims claims = Jwts.claims().setSubject(subject);
claims.put("roles",scope);
return Jwts.builder()
.setClaims(claims)
.setIssuer("http://uni.pu.com")
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 5*60*60*1000))
.signWith(SignatureAlgorithm.HS256, publicKey)
.compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (
username.equals(userDetails.getUsername())
&& !isTokenExpired(token));
}
}
JWTAuthenticationFilter class:
public class JWTAuthenticationFilter extends OncePerRequestFilter {
#Autowired
private MyUserDetailsService userDetailsService;
#Autowired
private JwtTokenUtil jwtTokenUtil;
public static final String TOKEN_PREFIX = "Bearer ";
public static final String HEADER_STRING = "Authorization";
#Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
throws IOException, ServletException {
String header = req.getHeader(HEADER_STRING);
String username = null;
String authToken = null;
if (header != null && header.startsWith(TOKEN_PREFIX)) {
authToken = header.replace(TOKEN_PREFIX, "");
try {
username = jwtTokenUtil.getUsernameFromToken(authToken);
} catch (IllegalArgumentException e) {
logger.error("an error occured during getting username from token", e);
} catch (ExpiredJwtException e) {
logger.warn("the token is expired and not valid anymore", e);
} catch (SignatureException e) {
logger.error("Authentication Failed. Username or Password not valid.");
}
} else {
logger.warn("couldn't find bearer string, will ignore the header");
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(authToken, userDetails)) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(req));
logger.info("authenticated user " + username + userDetails.getAuthorities()+ ", setting security context");
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
chain.doFilter(req, res);
}
}
WebSecurityConfig:
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, jsr250Enabled = true, prePostEnabled = true)
public class WebSecurityConfig {
private static final String[] WHITE_LIST_URLS = { "/", "/api/students/register", "/api/students/register/*",
"/api/user/reset-password", "/api/user/save-password", "/api/user/login", "/api/user/logout",
// -- Swagger UI v2
"/v2/api-docs", "/swagger-resources", "/swagger-resources/**", "/configuration/ui", "/configuration/**",
"/configuration/security", "/swagger-ui.html", "/webjars/**",
// -- Swagger UI v3 (OpenAPI)
"/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui/", "/swagger-ui" };
#Autowired
private JwtAuthenticationEntryPoint authenticationEntryPoint;
#Value("${jwt.public.key}")
private RSAPublicKey rsaPublicKey;
#Value("${jwt.private.key}")
private RSAPrivateKey rsaPrivateKey;
#Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder(10);
}
#Bean
public JWTAuthenticationFilter authenticationTokenFilterBean() throws Exception {
return new JWTAuthenticationFilter();
}
#Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// Enable CORS and disable CSRF
http = http.cors().and().csrf().disable();
// Set session management to stateless
http = http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and();
// Set unauthorized requests exception handler
http = http.exceptionHandling(
(exceptions) -> exceptions.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(new BearerTokenAccessDeniedHandler()));
http = http.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
http = http.formLogin().disable();
// Set permissions on endpoints
http.authorizeHttpRequests().antMatchers(WHITE_LIST_URLS).permitAll()
// private endpoints
.anyRequest().authenticated()
// Set up oauth2 resource server
.and().httpBasic(Customizer.withDefaults()).oauth2ResourceServer().jwt();
return http.build();
}
#Bean
public JwtEncoder jwtEncoder() {
JWK jwk = new RSAKey.Builder(this.rsaPublicKey).privateKey(this.rsaPrivateKey).build();
JWKSource<SecurityContext> jwks = new ImmutableJWKSet<>(new JWKSet(jwk));
return new NimbusJwtEncoder(jwks);
}
// Used by JwtAuthenticationProvider to decode and validate JWT tokens
#Bean
public JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withPublicKey(this.rsaPublicKey).build();
}
// Extract authorities from the roles claim
#Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName("roles");
jwtGrantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
return jwtAuthenticationConverter;
}
#Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
#Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration)
throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
#Bean
public RoleHierarchy roleHierarchy() {
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
String hierarchy = "ROLE_ADMIN > ROLE_INSPECTOR \n ROLE_INSPECTOR > ROLE_STUDENT";
roleHierarchy.setHierarchy(hierarchy);
return roleHierarchy;
}
}
MyUserDetailsService:
#Service
#Transactional
public class MyUserDetailsService implements UserDetailsService {
#Autowired
private UserRepository userRepository;
public UserDetails loadUserByUsername(String email) throws UserNotFoundException {
Optional<User> optionalUser = userRepository.findByEmail(email);
if(!optionalUser.isPresent()) {
throw new UserNotFoundException("No user found with email: " + email);
}
User user = optionalUser.get();
boolean enabled = user.isEnabled();
boolean accountNonExpired = true;
boolean credentialsNonExpired = true;
boolean accountNonLocked = true;
return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPassword(),
enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, getAuthorities(user.getRoles()));
}
public Collection<? extends GrantedAuthority> getAuthorities(Collection<Role> roles) {
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
for (Role role : roles) {
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
return authorities;
}
}
I'm doing a private api in java, jwt, spring security and the first time come in the request a json object.
user: xxx
password: yyy
The api return a jwt token in the body.
The others call the token come in the body json and to validate it I use this:
sbody = servletRequest.getReader().lines().collect(Collectors.joining());
To get the field token and it get ok, but then of the filter it show the message:
"Required request body is missing: public org.springframework.http.ResponseEntity"
This is my api:
#SpringBootApplication
public class JwtApplication {
public static void main(String[] args) {
SpringApplication.run(JwtApplication.class, args);
}
#EnableWebSecurity
#Configuration
class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.addFilterAfter(new JWTAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests().antMatchers(HttpMethod.POST, "/user").permitAll()
.antMatchers(HttpMethod.POST, "/Autenticacion").permitAll().anyRequest().authenticated();
}
}
}
This is the filter:
public class JWTAuthorizationFilter extends OncePerRequestFilter {
private final String HEADER = "Authorization";
private final String SESSION = "sesion";
private final String PREFIX = "Bearer ";
private final String SECRET = "mySecretKey";
public static final long EXPIRATION_TIME = 900_000; // 15 mins
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
try {
boolean resultado_checktoken = checkJWTToken(httpRequest, httpResponse);
if (resultado_checktoken) {
Claims claims = validateToken(request);
if (claims.get("authorities") != null) {
setUpSpringAuthentication(claims);
} else {
SecurityContextHolder.clearContext();
}
} else {
SecurityContextHolder.clearContext();
}
chain.doFilter(request, response);
} catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException e) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
return;
}
System.out.println("supuestamente no hubo problemas");
}
private Claims validateToken(HttpServletRequest request) {
//String jwtToken = request.getHeader(HEADER).replace(PREFIX, "");
String jwtToken="";
try {
jwtToken = this.getBodySession(request);
} catch (IOException e) {
e.printStackTrace();
};
return Jwts.parser().setSigningKey(SECRET.getBytes()).parseClaimsJws(jwtToken).getBody();
}
/**
* Authentication method in Spring flow
*
* #param claims
*/
private void setUpSpringAuthentication(Claims claims) {
#SuppressWarnings("unchecked")
List<String> authorities = (List<String>) claims.get("authorities");
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(claims.getSubject(), null,
authorities.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()));
SecurityContextHolder.getContext().setAuthentication(auth);
}
private boolean checkJWTToken(HttpServletRequest request, HttpServletResponse res) throws IOException {
String authenticationHeader = "";
authenticationHeader = this.getBodySession(request);
if (authenticationHeader == null || !authenticationHeader.startsWith(PREFIX))
return false;
return true;
}
public String getBodySession(HttpServletRequest request) throws IOException {
String sbody = "";
HttpServletRequest servletRequest = new ContentCachingRequestWrapper(request);
//servletRequest.getParameterMap();
sbody = servletRequest.getReader().lines().collect(Collectors.joining());
String Field = SESSION;
String scampo = "";
if (sbody.contains(Field)) {
scampo = sbody.substring(sbody.indexOf(Field), sbody.indexOf("\n", sbody.indexOf(Field)))
.replace(Field + "\": \"", "").replace("\"", "").replace(",", "");
}
System.out.println("sbody: " + sbody + " sesion: " + scampo);
return scampo;
}
}
This needs to return a boolean explicitly you cannot have two return statements.
private boolean checkJWTToken(HttpServletRequest request, HttpServletResponse res) throws IOException {
String authenticationHeader = "";
authenticationHeader = this.getBodySession(request);
if (authenticationHeader == null || !authenticationHeader.startsWith(PREFIX))
**return false;**
**return true;**
}
I am new to JWT. I create my own microservice and want to introduce JWT authentication. I have one website that issues a token and in the other I want to check the correctness of this token. I want to do this without connecting the second site to db. This approach seems to me appropriate and best for user data.
I have following payload of token:
{
"sub": "Marek",
"auth": [
{
"authority": "ROLE_USER"
}
],
"iat": 1574091010,
"exp": 1574091210
}
Its my code:
WebSecurityConfig
#Autowired
private JwtTockenCreator jwtTockenCreator;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.authorizeRequests()
.antMatchers("/user/login").permitAll()
.antMatchers("/user/addUser").permitAll()
.anyRequest()
.authenticated();
http.exceptionHandling().accessDeniedPage("/login");
http.apply(new JWTConfigurer(jwtTockenCreator));
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
}
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
JwtTokenFilter
public class JwtTokenFilter extends OncePerRequestFilter {
private JwtTockenCreator jwtTockenCreator;
public JwtTokenFilter(JwtTockenCreator jwtTockenCreator) {
this.jwtTockenCreator = jwtTockenCreator;
}
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
FilterChain filterChain) throws ServletException, IOException {
String token = jwtTockenCreator.resolveToken(httpServletRequest);
try {
if (token != null && jwtTockenCreator.validateToken(token)) {
Authentication auth = jwtTockenCreator.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(auth);
}
} catch (CustomException ex) {
// this is very important, since it guarantees the user is not authenticated at
// all
SecurityContextHolder.clearContext();
httpServletResponse.sendError(ex.getHttpStatus().value(), ex.getMessage());
return;
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
JwtTockenCreator
public class JwtTockenCreator {
#Value("${security.secretKey}")
private String secretKey;
#PostConstruct
protected void init() {
secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
}
public Authentication getAuthentication(String token) {
Claims claims = Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token)
.getBody();
Collection<? extends GrantedAuthority> authorities = Arrays.asList(claims.get(secretKey).toString().split(",")).stream()
.map(authority -> new SimpleGrantedAuthority(authority)).collect(Collectors.toList());
User principal = new User(claims.getSubject(), "", authorities);
return new UsernamePasswordAuthenticationToken( principal,"",authorities);
}
public String getUsernameFromToken(String token) {
return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
}
public String resolveToken(HttpServletRequest req) {
String bearerToken = req.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
throw new CustomException("Expired or invalid JWT token", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
JWTConfigurer
public class JWTConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private JwtTockenCreator jwtTockenCreator;
public JWTConfigurer(JwtTockenCreator jwtTockenCreator) {
this.jwtTockenCreator = jwtTockenCreator;
}
#Override
public void configure(HttpSecurity http) throws Exception {
JwtTokenFilter customFilter = new JwtTokenFilter(jwtTockenCreator);
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
}
}
UserController
#CrossOrigin
#RestController
#RequestMapping("/user")
public class UserController {
#Autowired
RestTemplate restTemplate;
#Value("${hostname}")
public String hostname;
#Value("${user.port}")
public String userPort;
#PostMapping("/login")
public ResponseEntity<String> login(#RequestBody User user) {
String urlUser = hostname + userPort + "/user/login";
String token = restTemplate.postForObject(urlUser, user, String.class);
return ResponseEntity.ok(token);
}
#PreAuthorize("hasRole('USER')")
#PostMapping("/addUser")
public ResponseEntity<String> registerAction(#RequestBody User user) {
String urlUser = hostname + userPort + "/user/addUser";
String token = restTemplate.postForObject(urlUser, user, String.class);
return ResponseEntity.ok(token);
}
}
In Eclipse doesn't give any errors. That's why I don't know what I'm doing wrong
when I want to call / user / addUser and add a new user nothing happens. In the User service I call, I have a function responsible for adding users and it works correctly when I refer to it directly. And if I want to do it through my Rest Api it doesn't work anymore. And it is my problem that I do not know what can happen because I have no mistake. I remind you that I am still learning and I am asking for understanding
I am writing rest service with json. For backend I use Spring Security. I have form witch sends with ajax rest object as follow:
{email: "admin", password: "secret"}
Now on the server I have configuration as follow:
#Configuration
#EnableWebSecurity
#ComponentScan("pl.korbeldaniel.cms.server")
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
#Autowired
private RestAuthenticationSuccessHandler authenticationSuccessHandler;
#Autowired
private RestAuthenticationFailureHandler authenticationFailureHandler;
#Bean
JsonAuthenticationFilter jsonAuthenticationFilter() throws Exception {
JsonAuthenticationFilter filter = new JsonAuthenticationFilter();
filter.setAuthenticationManager(authenticationManagerBean());
System.out.println("jsonAuthenticationFilter");
return filter;
}
#Bean
public RestAuthenticationSuccessHandler mySuccessHandler() {
return new RestAuthenticationSuccessHandler();
}
#Override
#Autowired
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("admin").password("secret").roles("ADMIN");
// auth.jdbcAuthentication().
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(jsonAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
http.csrf().disable();//
http.exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint)//
.and().authorizeRequests()//
.antMatchers("/").permitAll()//
.antMatchers("/services/anonymous/**").permitAll()//
.antMatchers("/services/authenticated/**").authenticated()//
.and().formLogin().loginProcessingUrl("/services/anonymous/loginService/login").usernameParameter("email").passwordParameter("password")//
.successHandler(authenticationSuccessHandler)//
.and().logout().logoutUrl("/services/anonymous/loginService/logout");
// http.httpBasic();
}
}
Problem is that spring security demands from me to send credentials as body, but I would like to spring accept my Json object.
So I've wrote my own authentication filter base on this:
#Component
public class JsonAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private boolean postOnly;
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
System.out.println("attemptAuthentication");
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
UsernamePasswordAuthenticationToken authRequest = this.getUserNamePasswordAuthenticationToken(request);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
/**
* #param request
* #return
*/
private UsernamePasswordAuthenticationToken getUserNamePasswordAuthenticationToken(HttpServletRequest request) {
// TODO Auto-generated method stub
System.out.println(request);
return null;
}
}
But unfortunatelly this filter seems to not work.
When I send ajax post request from login form, I am getting 302 Found and then I am getting this:
Remote Address:127.0.0.1:8080
Request URL:http://localhost:8080/cms/login?error
Request Method:GET
Status Code:404 Not Found
Like there fail to validate user credential (cause form body is empty and credentials goes as json), and then it redirect to login?error which doesn't exist cause I've my own login form.
Please help.
Edit
public class WebServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] { SecurityConfig.class };
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { WebConfig.class };
}
#Override
protected String[] getServletMappings() {
// return new String[] { "/" };
// return new String[] { "/cms/" };
return new String[] { "/services/*" };
}
}
#EnableWebMvc
#ComponentScan(basePackages = "pl.daniel.cms.server")
public class WebConfig extends WebMvcConfigurerAdapter {
}
Well, it must not work until you write the getUserNamePasswordAuthenticationToken body.
Actually, you must read the request body of the HttpServletRequest, parse it through Jackson or any other mapping way and create with it the UsernamePasswordAuthenticationToken.
Using Jackson (choose the right version depending on your Spring version), I would create a simple bean like this:
#JsonIgnoreProperties(ignoreUnkown=true)
public LoginRequest{
private String email;
private String password;
// getters & setters
}
The use it to map it the request body:
private UsernamePasswordAuthenticationToken getUserNamePasswordAuthenticationToken(HttpServletRequest request) throws IOException{
StringBuffer sb = new StringBuffer();
BufferedReader bufferedReader = null;
String content = "";
LoginRequest sr = null;
try {
bufferedReader = request.getReader()
char[] charBuffer = new char[128];
int bytesRead;
while ( (bytesRead = bufferedReader.read(charBuffer)) != -1 ) {
sb.append(charBuffer, 0, bytesRead);
}
content = sb.toString();
ObjectMapper objectMapper = new ObjectMapper();
try{
sr = objectMapper.readValue(content, LoginRequest.class);
}catch(Throwable t){
throw new IOException(t.getMessage(), t);
}
} catch (IOException ex) {
throw ex;
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException ex) {
throw ex;
}
}
}
return new UsernamePasswordAuthenticationToken(sr.getEmail(), sr.getPassword());
}
P.D. Yo must use Post, you will never be able to post a request-body using GET
You can extend and override WebSecurityConfigurerAdapter
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.addFilter(new UserNamePasswordAuthFilter(authenticationManager(), userRepo))
.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest()
.authenticated()
.and()
.httpBasic();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProvider());
}
#Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService); // custom user service
provider.setPasswordEncoder(passwordEncoder); // custom password encoder
return provider;
}
Then you can define a filter for authentication and optionally you can override successful login behavior.
public class UserNamePasswordAuthFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authManager;
private final AecUserRepo userRepo;
public UserNamePasswordAuthFilter(AuthenticationManager authManager, AecUserRepo userRepo) {
super();
this.authManager = authManager;
this.userRepo = userRepo;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
try {
// Get username & password from request (JSON) any way you like
UsernamePassword authRequest = new ObjectMapper()
.readValue(request.getInputStream(), UsernamePassword.class);
Authentication auth = new UsernamePasswordAuthenticationToken(authRequest.getUsername(),
authRequest.getPassword());
return authManager.authenticate(auth);
} catch (Exception exp) {
throw new RuntimeException(exp);
}
}
#Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, Authentication authResult)
throws IOException, ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
+ authResult);
}
// custom code
SecurityContextHolder.getContext().setAuthentication(authResult);
}
}