Spring Cloud Netflix Zuul routing with account service - java

I am developing a microservices web application.
I have this microservices:
frontEnd service (all html files)
Account service (with Spring Security)
ZuulGateway service
Demo service (a simple rest controller that return a string)
EurekaServer service
My account service works. The login phase works and the JWT creation works. I save it in the header of the response:
response.addHeader("Authorization", "Bearer " + token);
This is the Ajax code in the frontEnd:
/* sign in submit function */
$("#submit").click(function(e) {
e.preventDefault();
$.ajax({ /* Ajax call to AccountMicroservice for login */
url : 'http://localhost:8762/token/generate-token',
type : "POST",
data : {
username : $("#username").val(),
password : $("#password").val()
},
success : function(data) {
console.log(data.token);
window.location.href = 'http://localhost:8762/test';
},
error : function(result) {
alert("Sign in failed!");
console.log(result);
}
});
});
The problems arose with the addition of Spring Cloud Netflix Zuul. The gateway server port is 8762.
Each request to 'http://localhost:8762/token/generate-token' return a 401 error.
These are the Zuul gateway service. I omitted the import.
application.properties:
server.port=8762
spring.application.name=gateway-service
eureka.client.serviceUrl.defaultZone=${EUREKA_SERVER_URL:http://localhost:8761/eureka}
eureka.client.register-with-eureka=true
eureka.client.fetch-registry=true
zuul.routes.test-service.path=/test/**
zuul.routes.test-service.service-id=test-service
zuul.routes.account-service.path=/token/**
zuul.routes.account-service.service-id=account-service
zuul.routes.auth-service.sensitive-headers=Cookie,Set-Cookie
webSecurityConfig:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler)
.and()
.addFilterAfter(new JwtTokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
// allow all who are accessing "auth" service
.antMatchers(HttpMethod.POST, "/token/**", "/signup").permitAll()
// Any other request must be authenticated
.anyRequest().authenticated();
}
//The CORS filter bean - Configures allowed CORS any (source) to any
//(api route and method) endpoint
#Bean
CorsConfigurationSource corsConfigurationSource() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin(CorsConfiguration.ALL);
//config.addAllowedHeaders(Collections.singletonList(CorsConfiguration.ALL));
config.addAllowedMethod("OPTIONS");
config.addAllowedMethod("HEAD");
config.addAllowedMethod("GET");
config.addAllowedMethod("PUT");
config.addAllowedMethod("POST");
config.addAllowedMethod("DELETE");
config.addAllowedMethod("PATCH");
source.registerCorsConfiguration("/**", config);
return source;
}
}
JwtTokenUtil:
#Component
public class JwtTokenUtil implements Serializable {
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(SIGNING_KEY)
.parseClaimsJws(token)
.getBody();
}
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
public String generateToken(Authentication authentication) {
final String authorities = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(","));
return Jwts.builder()
.setSubject(authentication.getName())
.claim(AUTHORITIES_KEY, authorities)
.signWith(SignatureAlgorithm.HS256, SIGNING_KEY)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + ACCESS_TOKEN_VALIDITY_SECONDS*1000))
.compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (
username.equals(userDetails.getUsername())
&& !isTokenExpired(token));
}
UsernamePasswordAuthenticationToken getAuthentication(final String token, final Authentication existingAuth, final UserDetails userDetails) {
final JwtParser jwtParser = Jwts.parser().setSigningKey(SIGNING_KEY);
final Jws<Claims> claimsJws = jwtParser.parseClaimsJws(token);
final Claims claims = claimsJws.getBody();
final Collection<? extends GrantedAuthority> authorities =
Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
return new UsernamePasswordAuthenticationToken(userDetails, "", authorities);
}
}
JwtTokenAuthenticationFilter:
public class JwtTokenAuthenticationFilter extends OncePerRequestFilter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
// 1. get the authentication header. Tokens are supposed to be passed in the authentication header
String header = request.getHeader(HEADER_STRING);
String username = null;
String authToken = null;
// 2. validate the header and check the prefix
if (header != null && header.startsWith(TOKEN_PREFIX)) {
// 3. Get the token
authToken = header.replace(TOKEN_PREFIX,"");
try {
username = jwtTokenUtil.getUsernameFromToken(authToken);
System.out.println("\n\n\n\n\n\n\n\n\n\n" + username + "\n\n\n\n\n\n\n\n\n\n");
} catch (IllegalArgumentException e) {
logger.error("c'รจ stato un errore durante il reperimento dello username dal token", e);
}
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// 4. Validate the token
if (jwtTokenUtil.validateToken(authToken, userDetails)) {
//UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN")));
UsernamePasswordAuthenticationToken authentication = jwtTokenUtil.getAuthentication(authToken, SecurityContextHolder.getContext().getAuthentication(), userDetails);
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
logger.info("authenticated user " + username + ", setting security context");
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
// go to the next filter in the filter chain
chain.doFilter(request, response);
}
}
JwtAuthenticationEntryPoint:
#Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
String json = String.format("{\"message\": \"%s\"}", authException.getMessage());
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(json);
}
}
constants:
public class Constants {
public static final long ACCESS_TOKEN_VALIDITY_SECONDS = 5*60*60;
public static final String SIGNING_KEY = "devglan123r";
public static final String TOKEN_PREFIX = "Bearer ";
public static final String HEADER_STRING = "Authorization";
public static final String AUTHORITIES_KEY = "scopes";
}

Related

Spring security custom UsernamePasswordAuthenticationFilter not replacing default UsernamePasswordAuthenticationFilter

I'm trying to implement my own UsernamePasswordAuthenthicationFilter that authenticates every request from my frontend using firebase auth.
public class FireBaseAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
.
.
//Assigning roles happens here
List<GrantedAuthority> authorities = new ArrayList<>();
if (user.getCustomClaims() != null) {
if (user.getCustomClaims().get("boss").equals("true")) {
authorities.add(new SimpleGrantedAuthority("boss"));
}
if (user.getCustomClaims().get("admin").equals("true")) {
authorities.add(new SimpleGrantedAuthority("admin"));
}
if (user.getCustomClaims().get("office").equals("true")) {
authorities.add(new SimpleGrantedAuthority("office"));
}
if (user.getCustomClaims().get("warehouse").equals("true")) {
authorities.add(new SimpleGrantedAuthority("warehouse"));
}
if (user.getCustomClaims().get("store").equals("true")) {
authorities.add(new SimpleGrantedAuthority("store"));
}
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(user.getEmail(), user.getUid(), authorities));
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
user.getEmail(),
user.getUid(),
authorities
);
}
filterChain.doFilter(request, response);
}
}
Then i try and replace the default auth in my security config:
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().disable().csrf().disable().httpBasic().disable().formLogin().disable()
.addFilter(new FireBaseAuthenticationFilter())
.sessionManagement().sessionCreationPolicy(STATELESS).and()
.authorizeRequests()
.anyRequest().authenticated();
}
}
But for some reason my custom filter is never called during runtime? What am I missing here?
Example :
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().cors().and().authorizeRequests()
.antMatchers(HttpMethod.POST, ChallengeConstant.AUTHORIZE_ENDPOINT).permitAll()
.antMatchers(HttpMethod.POST, ChallengeConstant.TOKEN_ENDPOINT).permitAll()
.antMatchers(HttpMethod.GET, "/*").permitAll()
.antMatchers(HttpMethod.GET, "/assets/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(new JWTFilter(userService, objectMapper), UsernamePasswordAuthenticationFilter.class)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
If you want to validate token :
#AllArgsConstructor
public class JWTFilter extends OncePerRequestFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(JWTFilter.class);
private final UserService userService;
private final ObjectMapper objectMapper;
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
String token = httpServletRequest.getHeader(ChallengeConstant.AUTH_HEADER);
if (token != null) {
LOGGER.info("The request is authenticated. Performing Token validity");
String userName;
try {
userName = JWT.require(Algorithm.HMAC512(ChallengeConstant.DUMMY_SIGN.getBytes()))
.build()
.verify(token.replace(ChallengeConstant.TOKEN_PREFIX, ""))
.getSubject();
} catch (JWTVerificationException ex) {
LOGGER.warn(String.format("Token is not valid. Token: %s", token), ex);
generateErrorResponse(httpServletResponse, ExceptionResponse.UNAUTHORIZED);
return;
}
LOGGER.info("Token is valid for username: {}", userName);
try {
UserEntity userEntity = userService.findUserByName(userName);
List<GrantedAuthority> authList = userEntity.getAuthorizations()
.stream()
.map(authorizationEntity -> new SimpleGrantedAuthority(authorizationEntity.getAuth()))
.collect(Collectors.toList());
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken(userEntity.getUserName(), userEntity.getPassword(), authList));
LOGGER.debug("User has been found by given username: {} with authorities: {}", userName, authList.toString());
} catch (NotFoundException ex) {
LOGGER.warn("User couldn't be found with given username: {}", userName);
generateErrorResponse(httpServletResponse, ExceptionResponse.NOT_FOUND);
return;
}
}
LOGGER.info("The request is NOT authenticated. It will continue to request chain.");
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
private void generateErrorResponse(HttpServletResponse httpServletResponse, ExceptionResponse exceptionResponse) throws IOException {
LOGGER.trace("Generating http error response");
httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
httpServletResponse.setStatus(exceptionResponse.getStatus().value());
ErrorResource errorResource = new ErrorResource(exceptionResponse.getCode(),
exceptionResponse.getMessage());
httpServletResponse.getWriter().write(objectMapper.writeValueAsString(errorResource));
LOGGER.trace("Error response is {}", errorResource.toString());
httpServletResponse.getWriter().flush();
}
I think you can validate in Filter and return error response if it is not valid.
Your custom implementation extends the UsernamePasswordAuthenticationFilter (which in its turn extends the AbstractAuthenticationProcessingFilter). The UsernamePasswordAuthenticationFilter, by default, is used for .formLogin authentication, handling the default AntRequestMatcher "/login". If you use a different protected endpoint, the filter's attemptAuthentication() method never gets action. So, if you want to use a different matcher (a different protected endpoint), you have to override the default AntRequestMatcher. For instance, you can do so within your custom filter constructor, by using something like that:
super.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/auth/signin", "GET"));

JWT authentication with spring and angular with null header

I am trying to do JWT token authentication using spring boot and angular. After login bearer token is created but after that in JWTAuthorizationFilter i am getting null header and because of that it return anonymousUser. Please tell me why i am getting null header.
SecurityConfig.java
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
#Autowired
private CustomUserDetailService customUserDetailService;
#Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.
cors().configurationSource(request -> new CorsConfiguration().applyPermitDefaultValues())
.and().csrf().disable()
.authorizeRequests()
.antMatchers("/**").permitAll()
.antMatchers("/manage/**").hasRole("ADMIN")
.antMatchers("/").hasRole("USER")
.and()
.exceptionHandling()
.accessDeniedPage("/access-denied")
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager(), customUserDetailService));
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailService).passwordEncoder(new
BCryptPasswordEncoder());
}
}
JWTAuthenticationFilter.java
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
try {
UserDetail user = new ObjectMapper().readValue(request.getInputStream(), UserDetail.class);
return this.authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(user.getEmail(), user.getPassword()));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
#Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authResult) throws IOException, ServletException {
String username = ((org.springframework.security.core.userdetails.User) authResult.getPrincipal()).getUsername();
String token = Jwts
.builder()
.setSubject(username)
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
String bearerToken = TOKEN_PREFIX + token;
System.out.println(bearerToken);
response.getWriter().write(bearerToken);
response.addHeader(HEADER_STRING, bearerToken);
}
}
JWTAuthorizationFilter.java
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
private final CustomUserDetailService customUserDetailService;
public JWTAuthorizationFilter(AuthenticationManager authenticationManager, CustomUserDetailService customUserDetailService) {
super(authenticationManager);
this.customUserDetailService = customUserDetailService;
}
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
String header = request.getHeader(HEADER_STRING);
if (header == null || !header.startsWith(TOKEN_PREFIX)) {
chain.doFilter(request, response);
return;
}
UsernamePasswordAuthenticationToken authenticationToken = getAuthenticationToken(request);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
chain.doFilter(request, response);
}
private UsernamePasswordAuthenticationToken getAuthenticationToken(HttpServletRequest request) {
String token = request.getHeader(HEADER_STRING);
if (token == null) return null;
String username = Jwts.parser().setSigningKey(SECRET)
.parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
.getBody()
.getSubject();
UserDetails userDetails = customUserDetailService.loadUserByUsername(username);
return username != null ?
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities())
: null;
}
}
CustomUserDetailService.java
#Component
public class CustomUserDetailService implements UserDetailsService {
private List<GrantedAuthority> role;
#Autowired
private UserDAO userDAO;
/*
* #Autowired public CustomUserDetailService(UserRepository userRepository) {
* this.userRepository = userRepository; }
*/
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = Optional.ofNullable(userDAO.getByEmail(username))
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
List<GrantedAuthority> authorityListAdmin = AuthorityUtils.createAuthorityList("ROLE_ADMIN");
List<GrantedAuthority> authorityListUser = AuthorityUtils.createAuthorityList("ROLE_USER");
if (user.getRole() == "admin") {
role = authorityListAdmin;
} else {
role = authorityListUser;
}
return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPassword(), role);
}
}
Userinfo.java
private String email;
private String role;
private String password;
Controller
#RequestMapping(value = "/login")
public ModelAndView login(
#RequestParam(name = "error", required = false) String error,
#RequestParam(name = "logout", required = false) String logout,
HttpServletRequest request,
HttpServletResponse response) {
ModelAndView mv = new ModelAndView("login");
HttpSession session= request.getSession(false);
Authentication auth = SecurityContextHolder.getContext()
.getAuthentication();
System.out.println("auth ===" + auth);
System.out.println("logout ===" + logout);
return mv;
}
This is the output on console:
Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJidW50QGdtYWlsLmNvbSIsImV4cCI6MTU5NjExMjcwM30.fBFMDO--8Q_56LT_qbioiT6p3BOxk3L9OrPVTw5EGbf7oJ0ky7W7PuahIYcdjYSL6-OsHY6qq8tPEetlJO7nEg
auth ===org.springframework.security.authentication.AnonymousAuthenticationToken#823df96a: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#b364: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS
Please tell me what i am missing here.
Your JWTAuthenticationFilter that extends UsernamePasswordAuthenticationFilter overrides successfulAuthentication method which by default calls this line:
SecurityContextHolder.getContext().setAuthentication(authResult);
Your implementation do not have this line so after processing of this filter you still do not have Authentication in Spring context. The next filter that is called is your JWTAuthorizationFilter which tries to read header from same request object as in previous filter. JWTAuthenticationFilter sets this header in response object not in request object. So basically you ends up without authentication because if (header == null || !header.startsWith(TOKEN_PREFIX)) is always true after login flow.
First thing, in the authentication filter token generated and set on the HttpServletResponse header not on the request object's header. Then next the authorization filter checking the request header for token, so there may be the issue of null happened.
Usually authentication and authorization will not be chained like this, but don't know regarding the actual use case you were trying to implement.

How to get username and uuid by bearer token from keycloak programmaticaly (Spring)?

I have Spring application with keycloak dependency.
Front-end send to my back-end bearer token and I want to get username and his UUID from keycloak using this token.
Here is my keycloak configuration.
#Configuration
#ComponentScan(
basePackageClasses = KeycloakSecurityComponents.class,
excludeFilters = #ComponentScan.Filter(type = FilterType.REGEX, pattern = "org.keycloak.adapters.springsecurity.management.HttpSessionManager"))
#EnableWebSecurity
class KeycloakConfig extends KeycloakWebSecurityConfigurerAdapter {
#Bean
public KeycloakConfigResolver keycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new NullAuthenticatedSessionStrategy();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(keycloakAuthenticationProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.csrf().disable()
.sessionManagement()
.and()
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMINS")
.antMatchers("/user/**").hasRole("USER")
.anyRequest().permitAll();
}
}
And in this endpoint I am getting authorization header:
#PostMapping(value = "/save/{title}")
#ResponseBody
public ResponseEntity uploadFile(#RequestParam("file") MultipartFile file, #PathVariable("title") String title, #RequestHeader("Authorization") String authHeader) {
//get user by token?
}
Shortly, I do parse public key and access token to get AccessToken class, which has all you need ( accessToken.getId() and accessToken.getPreferredUsername() )
#Autowired
private KeycloakSpringBootProperties keycloakProperties;
public String getRealm() {
return keycloakProperties.getRealm();
}
public String getAuthServerUrl() {
return keycloakProperties.getAuthServerUrl();
}
public String getRealmUrl() {
return getAuthServerUrl()
+ "/realms/"
+ getRealm();
}
public String getOpenIdConnectUrl() {
return getRealmUrl() + "/protocol/openid-connect";
}
public String getOpenIdConnectCertsUrl() {
return getOpenIdConnectUrl() + "/certs";
}
public AccessToken loadAccessToken(String accessToken) throws TokenNotActiveException, VerificationException, NoSuchFieldException {
PublicKey publicKey = new KeyCloakRsaKeyLoader().getPublicKeyFromKeyCloak(getOpenIdConnectCertsUrl());
String realmUrl = keyCloakConnectionProvider.getRealmUrl();
AccessToken token =
RSATokenVerifier.verifyToken(
accessToken,
publicKey,
realmUrl,
true,
true);
return token;
}

Create JWT Token within UsernamePasswordAuthenticationFilter or Controller?

I try to generate a JWT Token with Spring Security but I don't how to do it correctly (with the best practice).
Do I should "intercept" the authenticate method within UsernamePasswordAuthenticationFilter somewhere and generate token inside it ?
Or it is better to use AuthenticationManager autowired in the controller '/login' ?
I'm afraid to authenticate the user twice if I use the controller mechanism.
I used this tutorial : tutorial Jwt Token
Here is my code :
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
UserService userService;
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
// #Autowired
// private UserDetailsService jwtUserDetailsService;
#Autowired
private JwtTokenFilter jwtTokenFilter;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.and()
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.cors().and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/css/**", "/login/**", "/register/**").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
//.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
.formLogin()
.usernameParameter("email")
//.loginPage("http://localhost:4200/login").failureUrl("/login-error")
.and()
.logout()
.permitAll();
http
.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
#Bean
public CustomDaoAuthenticationProvider authenticationProvider() {
CustomDaoAuthenticationProvider authenticationProvider = new CustomDaoAuthenticationProvider();
authenticationProvider.setPasswordEncoder(new BCryptPasswordEncoder());
authenticationProvider.setUserDetailsService(userService);
return authenticationProvider;
}
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebConfig() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins(
"http://localhost:4200")
.allowedMethods("GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS")
.allowedHeaders("Content-Type", "X-Requested-With", "accept", "Origin", "Access-Control-Request-Method",
"Access-Control-Request-Headers", "Authorization", "Cache-Control",
"Access-Control-Allow-Origin")
.exposedHeaders("Access-Control-Allow-Origin", "Access-Control-Allow-Credentials")
.allowCredentials(true).maxAge(3600);
}
};
}
}
Token Filter
public class JwtTokenFilter extends GenericFilterBean {
private JwtTokenProvider jwtTokenProvider;
public JwtTokenFilter(JwtTokenProvider jwtTokenProvider) {
this.jwtTokenProvider = jwtTokenProvider;
}
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) throws IOException, ServletException {
String token = jwtTokenProvider.resolveToken((HttpServletRequest) req);
if (token != null && jwtTokenProvider.validateToken(token)) {
Authentication auth = token != null ? jwtTokenProvider.getAuthentication(token) : null;
SecurityContextHolder.getContext().setAuthentication(auth);
}
filterChain.doFilter(req, res);
}
}
Token Provider
#Component
public class JwtTokenProvider {
#Value("${security.jwt.token.secret-key:secret}")
private String secretKey = "secret";
#Value("${security.jwt.token.expire-length:3600000}")
private long validityInMilliseconds = 3600000; // 1h
#Autowired
private UserDetailsService userDetailsService;
#PostConstruct
protected void init() {
secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
}
public String createToken(String username, List<String> roles) {
Claims claims = Jwts.claims().setSubject(username);
claims.put("roles", roles);
Date now = new Date();
Date validity = new Date(now.getTime() + validityInMilliseconds);
return Jwts.builder()//
.setClaims(claims)//
.setIssuedAt(now)//
.setExpiration(validity)//
.signWith(SignatureAlgorithm.HS256, secretKey)//
.compact();
}
public Authentication getAuthentication(String token) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(getUsername(token));
return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
}
public String getUsername(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, bearerToken.length());
}
return null;
}
public boolean validateToken(String token) {
try {
Jws<Claims> claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
if (claims.getBody().getExpiration().before(new Date())) {
return false;
}
return true;
} catch (JwtException | IllegalArgumentException e) {
throw new InvalidJwtAuthenticationException("Expired or invalid JWT token");
}
}
}
Given your context, the controller is responsible for issuing a new token (after validating credentials) while the filter is responsible for authenticating the user against the given token. The controller should not populate the security context (authenticate user), it is the filter's responsibility.
To better understand the two phases:
Spring uses two filters to authenticate and log in a user.
See UsernamePasswordAuthenticationFilter and SecurityContextPersistenceFilter in a "username/password" scenario, from the Spring Security project: the first one processes an authentication attempt (username/password) while the latter populates the security context from a SecurityContextRepository (from a session in general).

Spring Security and SiteMinder integration

I'm trying to integrate SiteMinder with Spring Security. I have a logout button on home page which is supposed to make http get request to backend. I'm trying to invalidate session and redirect back to home page. It's supposed to automatically navigate to log in page which is set up in apache. unfortunate it's not invalidating session or delete cookies.
This is my
WebSecurityConfigurerAdapter
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(ssoHeaderFilter(), RequestHeaderAuthenticationFilter.class)
.authenticationProvider(ssoAuthProvider())
.logout()
.logoutUrl("/logoutPage")
.logoutSuccessUrl("/login?logout")
.deleteCookies("JSESSIONID", "GPSESSION")
.invalidateHttpSession(true)
.and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/resources/**")
.permitAll()
.antMatchers( "/**")
.hasRole("ADMIN");
}
AuthenticationController
#RequestMapping(value="/logoutPage", method = RequestMethod.GET)
public void logoutPage (HttpServletRequest request, HttpServletResponse response) throws ServletException {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
HttpSession session = request.getSession(false);
request.logout();
}
SSOAuthenticationProvider
public class SSOAuthenticationProvider implements AuthenticationProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(WebSSOAuthenticationProvider.class);
public static final Map<UserAuthority, UserAuthority> roleMap = new HashMap<>();
static {
roleMap.put(UserAuthority.ADMIN, UserAuthority.ADMIN);
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
UserDetailsBean user = (UserDetailsBean) authentication.getPrincipal();
LOGGER.info("Inside WebSSOAuthenticationProvider authenticate method");
if(isValidRoles(user.getRoles())){
return new UsernamePasswordAuthenticationToken(user, authentication.getCredentials(),
getAuthoritiesByRoles( user.getRoles()) );
}
throw new BadCredentialsException(user.getFirstName() + " has not valid roles");
}
private boolean isValidRoles(Set<UserAuthority> roles) {
return roles != null && roles.stream().filter(roleMap::containsKey).findAny().isPresent();
}
#Override
public boolean supports(Class<?> authentication) {
return true;
}
private List<GrantedAuthority> getAuthoritiesByRoles(Set<UserAuthority> roles) {
List<GrantedAuthority> authorities = roles.stream().map(v -> v.name()).map(SimpleGrantedAuthority::new).collect(Collectors.toList());
return authorities;
}
SSOAuthenticationFilter
public class SSOAuthenticationFilter extends RequestHeaderAuthenticationFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(SSOAuthenticationFilter.class);
#Value("${project.ui.test.mode:false}")
private boolean guiTestMode;
private String FIRST_NAME = "sso-givenname";
private String LAST_NAME = "sso_surname";
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
LOGGER.debug("Working in the test mode for logging");
Enumeration<String> names = request.getHeaderNames();
LOGGER.info("Going to print all http headers");
while (names.hasMoreElements()) {
String name = names.nextElement();
String value = request.getHeader(name);
LOGGER.info("name: " + name + ", value: " + value);
}
UserDetailsBean user = new UserDetailsBean();
if(isTestMode()){
user.setFirstName("Mock");
user.setLastName("User");
user.setRoles(new HashSet<UserAuthority>(Arrays.asList(UserAuthority.ROLE_P_AND_S)));
request.getSession().setAttribute("user" , user);
return user;
}
user.setFirstName(request.getHeader(FIRST_NAME));
user.setLastName(request.getHeader(LAST_NAME));
user.setRoles(new HashSet<UserAuthority>(Arrays.asList(UserAuthority.ROLE_P_AND_S)));
request.getSession().setAttribute("user" , user);
return user;
}
private boolean isTestMode() {
return guiTestMode;
}
}

Categories

Resources