Spring security expression based access control - java

I'm using Spring Security to protect my resources. I would like to have an expression like following:
http.authorizeRequests().antMatchers("/refugee/{id}").hasRole("BUSINESS");
//Example
http.authorizeRequests().antMatchers("/refugee/{id}").hasRole("REFUGEE").and().access(principal.id==#id);
My SecurityConfig:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserService userService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(new TokenAuthenticationFilter(userService), AnonymousAuthenticationFilter.class);
http.csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);
//Options preflight
http.authorizeRequests().antMatchers(HttpMethod.OPTIONS).permitAll();
//ACL
http.authorizeRequests().antMatchers("/auth/**").anonymous();
http.authorizeRequests().antMatchers("/refugee/{id}").hasRole("BUSINESS");
http.authorizeRequests().antMatchers("/refugee/{id}").hasRole("REFUGEE").and();
http.authorizeRequests().antMatchers("/user/**").hasAnyRole("ADMIN", "REFUGEE", "BUSINESS");
}
}
And the TokenAuthenticationFilter:
public class TokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private UserService userService;
public TokenAuthenticationFilter(UserService userService) {
super("/");
this.userService = userService;
}
private final String HEADER_SECURITY_TOKEN = "Authorization";
private final String PARAMETER_SECURITY_TOKEN = "access_token";
private String token = "";
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
this.token = request.getHeader(HEADER_SECURITY_TOKEN);
if ("".equals(this.token) || this.token == null) {
this.token = request.getParameter(PARAMETER_SECURITY_TOKEN);
}
//Attempt to authenticate
Authentication authResult;
authResult = attemptAuthentication(request, response);
if (authResult == null) {
chain.doFilter(request, response);
} else {
successfulAuthentication(request, response, chain, authResult);
}
}
/**
* Attempt to authenticate request - basically just pass over to another
* method to authenticate request headers
*/
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
Authentication userAuthenticationToken = authUserByToken();
if (userAuthenticationToken == null) {
//throw new AuthenticationServiceException(MessageFormat.format("Error | {0}", "Bad Token"));
}
return userAuthenticationToken;
}
/**
* authenticate the user based on token, mobile app secret & user agent
*
* #return
*/
private Authentication authUserByToken() {
Authentication securityToken = null;
try {
// TODO - just return null - always fail auth just to test spring setup ok
User user = userService.findUserByAccessToken(this.token);
securityToken = new PreAuthenticatedAuthenticationToken(
user, null, user.getAuthorites());
} catch (Exception e) {
logger.error("Authenticate user by token error: ", e);
}
return securityToken;
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException {
SecurityContextHolder.getContext().setAuthentication(authResult);
chain.doFilter(request, response);
}
}

Related

Authorization failure in Springboot returns empty body with 403 status message

I have implemented a custom exception handler that handles all the exception that I am throwing:
#ControllerAdvice
public class AppExceptionHandler {
#ExceptionHandler(value = {UserServiceException.class})
public ResponseEntity<Object>handleUserServiceException(
UserServiceException ex, WebRequest request){
ErrorMessage errorMessage = new ErrorMessage(new Date(), ex.getMessage());
return new ResponseEntity<>(errorMessage, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR );
}
//A universal exception handling method
#ExceptionHandler(value = {Exception.class})
public ResponseEntity<Object>handleUserOtherExceptions(Exception ex, WebRequest request){
ErrorMessage errorMessage = new ErrorMessage(new Date(), ex.getMessage());
return new ResponseEntity<>(errorMessage, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR );
}
}
The Universal exception handler method doesn't gets called for 403 status response, the response body is empty but I want to see a response body.
Below is my Security class:
#EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter{
private final UserService userService;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
// #Autowired
// AuthenticationFailureHandler authenticationFailureHandler;
public WebSecurity(UserService userService, BCryptPasswordEncoder bCryptPasswordEncoder) {
this.userService = userService;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.POST, SecurityConstants.SIGN_UP_URL)
.permitAll()
.anyRequest()
.authenticated()
.and()
.addFilter(getAuthenticationFilter())
.addFilter(new AuthorizationFilter(authenticationManager()))
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService)
.passwordEncoder(bCryptPasswordEncoder);
}
public AuthenticationFilter getAuthenticationFilter() throws Exception{
final AuthenticationFilter filter = new AuthenticationFilter(authenticationManager());
filter.setFilterProcessesUrl("/users/login");
return filter;
}
Below is my AuthenticationFilter:
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter{
private final AuthenticationManager authenticationManager;
public AuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
throws AuthenticationException {
try {
UserLoginRequestModel creds = new ObjectMapper()
.readValue(req.getInputStream(), UserLoginRequestModel.class);
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
creds.getEmail(), creds.getPassword(), new ArrayList<>()));
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
#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(SecurityConstants.USER_ID, userDto.getUserId());
}
}
Below is my AuthorizationFilter:
public class AuthorizationFilter extends BasicAuthenticationFilter {
public AuthorizationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
String header = request.getHeader(SecurityConstants.HEADER_STRING);
if(header == null || !header.startsWith(SecurityConstants.TOKEN_PREFIX)) {
chain.doFilter(request, response);
return;
}
UsernamePasswordAuthenticationToken authentication = getAuthentication(request);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
String token = request.getHeader(SecurityConstants.HEADER_STRING);
if(token != null) {
token = token.replace(SecurityConstants.TOKEN_PREFIX, "");
String user = Jwts.parser()
.setSigningKey(SecurityConstants.getTokenSecret())
.parseClaimsJws(token)
.getBody()
.getSubject();
if(user != null) {
return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
}
}
return null;
}
}
Below is my postman response (As you can see the body is empty
I want Spring to handle all other exceptions by calling the overriding #ExceptionHandler I have written in the above AppExceptionHandler class.
As you can see the response body is empty. How do I return the default or custom object for response body?
Im not quite sure if this is what you are looking for, but If you want to somehow customize the messages from failed authentication have a look on AuthenticationEntryPoint like in https://www.baeldung.com/spring-security-basic-authentication. There you can react on the thrown exception and customize what message and codes to return.

Request body change when I Use servletRequest.getReader().lines().collect(Collectors.joining())

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;**
}

Spring REST Api respond with empty body + 403 Forbidden on runtime exceptions

I created a Spring Boot Rest Api with custom JWT authentication.
My problem is that when I'm sending for example a request with an expired, or invalid JWT token, I'm getting an exception like this:
com.auth0.jwt.exceptions.SignatureVerificationException: The Token's Signature resulted invalid when verified using the Algorithm: HmacSHA512
Which is obvoiusly OK, but the response body is empty and therefore the client has no clue why is the 403 error.
Problem is same with Spring's BadCredentials Exception etc...
How do I convert these exceptions into custom error responses instead of "403 forbidden"?
Spring Web Config:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final UserDetailsServiceImpl userDetailsService;
#Autowired
public WebSecurityConfig(UserDetailsServiceImpl userDetailsService) {
this.userDetailsService = userDetailsService;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
JWTAuthenticationFilter filter = new JWTAuthenticationFilter(authenticationManager());
filter.setFilterProcessesUrl(AUTH_URL);
http.cors().and().csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.POST, SIGN_UP_URL).permitAll()
.anyRequest().authenticated()
.and()
.addFilter(filter)
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
}
#Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
}
JWTAuthenticationFilter
private final AuthenticationManager authenticationManager;
#Autowired
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest req,
HttpServletResponse res) throws AuthenticationException {
try {
String decoded = new String(Base64.getDecoder().decode(new String(req.getInputStream().readAllBytes())));
AuthenticationDetails details = new Gson().fromJson(decoded, AuthenticationDetails.class);
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
details.getUsername(),
details.getPassword(),
new ArrayList<>()));
} catch (TokenExpiredException e) {
req.setAttribute("expired", e.getMessage());
throw new TokenExpiredException(e.getMessage());
} catch (Exception e){
throw new RuntimeException(e);
}
}
#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(Algorithm.HMAC512(SECRET.getBytes()));
res.addHeader(HEADER_STRING, TOKEN_PREFIX + token);
}
}
JWTAuthorizationFilter
public JWTAuthorizationFilter(AuthenticationManager authManager) {
super(authManager);
}
#Override
protected void doFilterInternal(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain) throws IOException, ServletException {
String header = req.getHeader(HEADER_STRING);
if (header == null || !header.startsWith(TOKEN_PREFIX)) {
chain.doFilter(req, res);
return;
}
UsernamePasswordAuthenticationToken authentication = getAuthentication(req);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(req, res);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
String token = request.getHeader(HEADER_STRING);
if (token != null) {
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;
}
}
If you are extending your JWTAuthenticationFilter from AbstractAuthenticationProcessingFilter, you can override unsuccessfulAuthenticationlike below:
#Override
protected void unsuccessfulAuthentication(
HttpServletRequest request, HttpServletResponse response, AuthenticationException failed)
throws IOException, ServletException {
SecurityContextHolder.clearContext();
failureHandler.onAuthenticationFailure(request, response, failed);
}
Now, as you can see, I've delegated the failure processing to my failureHandler which is of type org.springframework.security.web.authentication.AuthenticationFailureHandler.
For this you need to register your custom failure handler. You can do that by implementing your handler from org.springframework.security.web.authentication.AuthenticationFailureHandler and override onAuthenticationFailure, with checking the instance of your exception thrown from JWTAuthenticationFilter, like this:
#Component
public class MyAuthFailureHandler implements AuthenticationFailureHandler {
private final ObjectMapper mapper;
#Autowired
public MyAuthFailureHandler(ObjectMapper mapper) {
this.mapper = mapper;
}
#Override
public void onAuthenticationFailure(
HttpServletRequest request, HttpServletResponse response, AuthenticationException e)
throws IOException, ServletException {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
if (e instanceof BadCredentialsException) {
mapper.writeValue(
response.getWriter(),
ErrorResponse.of(
"Invalid username or password", ErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED));
} else if (e instanceof JwtExpiredTokenException) {
mapper.writeValue(
response.getWriter(),
ErrorResponse.of(
"Token has expired", ErrorCode.JWT_TOKEN_EXPIRED, HttpStatus.UNAUTHORIZED));
} else if (e instanceof AuthMethodNotSupportedException) {
mapper.writeValue(
response.getWriter(),
ErrorResponse.of(e.getMessage(), ErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED));
} else if (e instanceof TokenEncryptionException) {
mapper.writeValue(
response.getWriter(),
ErrorResponse.of(e.getMessage(), ErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED));
} else if (e instanceof InvalidJwtAuthenticationTokenException) {
mapper.writeValue(
response.getWriter(),
ErrorResponse.of(e.getMessage(), ErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED));
}
mapper.writeValue(
response.getWriter(),
ErrorResponse.of(
"Authentication failed", ErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED));
}

Springboot how to validate token on Zuul microservice

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

#WebFilter exclude url-pattern

I use a filter to check URL patterns for the logged in user.
But I have many URL patterns I need to filter.
{ "/table/*", "/user/*", "/contact/*", "/run/*", "/conf/*", ..., ..., ...}
It's becoming unmaintainable. It will be simpler just to exclude:
{ "/", "/login", "/logout", "/register" }
How can I achieve this?
#WebFilter(urlPatterns = { "/table/*","/user/*", "/contact/*","/run/*","/conf/*"})
public class SessionTimeoutRedirect implements Filter {
protected final Logger logger = LoggerFactory.getLogger("SessionFilter");
#Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (request.getSession().getAttribute("Id") != null) {
chain.doFilter(req, res);
} else {
logger.debug("session is null:"+request.getRequestURL());
response.sendRedirect(request.getContextPath()+"/login");
}
}
#Override
public void init(FilterConfig arg0) throws ServletException {
}
#Override
public void destroy() {
}
}
The servlet API doesn't support an "exclude" URL pattern.
Your best bet is to just map on /* and compare the HttpServletRequest#getRequestURI() against the set of allowed paths.
#WebFilter("/*")
public class LoginFilter implements Filter {
private static final Set<String> ALLOWED_PATHS = Collections.unmodifiableSet(new HashSet<>(
Arrays.asList("", "/login", "/logout", "/register")));
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
HttpSession session = request.getSession(false);
String path = request.getRequestURI().substring(request.getContextPath().length()).replaceAll("[/]+$", "");
boolean loggedIn = (session != null && session.getAttribute("Id") != null);
boolean allowedPath = ALLOWED_PATHS.contains(path);
if (loggedIn || allowedPath) {
chain.doFilter(req, res);
}
else {
response.sendRedirect(request.getContextPath() + "/login");
}
}
// ...
}
You can use initParam to have some excluded patterns and implement your logic. This is basically the same as BalusC's answer except by using initParam it can be written in the web.xml if you want/need to.
Below I am ignoring some binary (jpeg jpg png pdf) extensions:
#WebFilter(urlPatterns = { "/*" },
initParams = { #WebInitParam(name = "excludedExt", value = "jpeg jpg png pdf") }
)
public class GzipFilter implements Filter {
private static final Set<String> excluded;
#Override
public void init(FilterConfig filterConfig) throws ServletException {
String excludedString = filterConfig.getInitParameter("excludedExt");
if (excludedString != null) {
excluded = Collections.unmodifiableSet(
new HashSet<>(Arrays.asList(excludedString.split(" ", 0))));
} else {
excluded = Collections.<String>emptySet();
}
}
boolean isExcluded(HttpServletRequest request) {
String path = request.getRequestURI();
String extension = path.substring(path.indexOf('.', path.lastIndexOf('/')) + 1).toLowerCase();
return excluded.contains(extension);
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.print("GzipFilter");
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
if (isExcluded(httpRequest)) {
chain.doFilter(request, response);
return;
}
// Do your stuff here
}
}

Categories

Resources