Dedicated auth service in a springboot microservice - java

I am new to spring boot here. I have three micro-services one for user registration(authentication) and login(authorization) and two other services that requires authorization from the first.
Currently, the first microservice serves as the authentication and authorization point as no user can access the other two services without having passed through the first one.
Apparently, I retrieve the request header in the other two services to be able to get unique user details from the database and return unique resources.
#PostMapping("/clients")
public ResponseEntity<ResponseModel> addClient(#Valid #RequestBody Client client, #RequestHeader("Authorization") String auth) {
ClientRequest clientCreated = clientService.addClient(client, auth);
return handleSuccessResponseEntity("Client added successfully", HttpStatus.CREATED, clientCreated);
}
And also have authentication filter and authentication entry point to authenticate request in all the three services. I am not sure if this is a good implementation.
#Component
#Slf4j
public class JwtFilter extends OncePerRequestFilter {
#Autowired
private JWTUtility jwtUtility;
#Autowired
private ClientService clientService;
public static String token = null;
public static String userName = null;
public static Long userId = 0L;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String authorization = request.getHeader("Authorization");
try{
if (null != authorization && authorization.startsWith("Bearer ")) {
token = authorization.substring(7);
userName = jwtUtility.getEmailAddressFromToken(token);
userId = jwtUtility.getIdFromToken(token);
}
else{
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
ErrorResponse error = new ErrorResponse(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.toString(), "You are not authorized");
final ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(response.getOutputStream(), error);
}
}
catch (Exception e){
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
ErrorResponse error = new ErrorResponse(HttpStatus.FORBIDDEN.value(), HttpStatus.FORBIDDEN.toString(), e.getMessage());
final ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(response.getOutputStream(), error);
}
try{
if (0 != userId && null != userName && SecurityContextHolder.getContext().getAuthentication() == null) {
log.info("token "+token + "\n" + "userId " + userId);
UserDetails userDetails = clientService.getUserById(Long.valueOf(userId), token);
log.info("UserDTO {}", userDetails);
if (jwtUtility.validateTokenTwo(token)) {
log.info("token is valid");
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities()
);
usernamePasswordAuthenticationToken.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request)
);
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
filterChain.doFilter(request, response);
}
catch (HttpClientErrorException e){
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(e.getRawStatusCode());
ErrorResponse error = new ErrorResponse(e.getRawStatusCode(), String.valueOf(e.getRawStatusCode()), e.getStatusCode());
final ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(response.getOutputStream(), error);
}
}
}
#Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationEntryPoint.class);
#Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
AuthenticationException e) throws IOException, ServletException {
logger.error("Responding with unauthorized error. Message - {}", e.getMessage());
httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
ErrorResponse error = new ErrorResponse(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.toString(), "You are not authorized");
final ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(httpServletResponse.getOutputStream(), error);
}
}
However, What I would love to do if possible, is a dedicated service that will handle the auth and I can just decode the token from there without having to retrieve it from the header. In a nut-shell, to not have to retrieve token from the header.
Is this possible or what other way regards as standard can I take?

Related

Duplicate Json in Spring Boot Response

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.

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

Cannot get JWT Token from Zuul Header in Spring Boot Microservice Module

I have tried to send JWT Token in Zuul Header to another microservice module. Each Time request goes from zuul to another module but. I always get null header in another module. But I obtain token in zuul server from auth server but it never reaches to another module.
public class JwtTokenAuthenticationFilter extends OncePerRequestFilter {
private final JwtConfig jwtConfig;
public JwtTokenAuthenticationFilter(JwtConfig jwtConfig) {
this.jwtConfig = jwtConfig;
}
private static final int FILTER_ORDER = 0;
private static final boolean SHOULD_FILTER = true;
private static final Logger logger = LoggerFactory.getLogger(AuthenticationFilter.class);
#Override
protected void doFilterInternal(HttpServletRequest request1, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String header = request1.getHeader(jwtConfig.getHeader());
if (header == null || !header.startsWith(jwtConfig.getPrefix())) {
chain.doFilter(request1, response);
return;
}
/* new token getting code*/
String token = header.replace(jwtConfig.getPrefix(), "");
try {
Claims claims = Jwts.parser()
.setSigningKey(jwtConfig.getSecret().getBytes())
.parseClaimsJws(token)
.getBody();
String username = claims.getSubject();
System.out.println(username);
if (username != null) {
#SuppressWarnings("unchecked")
List<String> authorities = (List<String>) claims.get("authorities");
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(
username,
null, authorities.stream().map(
SimpleGrantedAuthority::new
).collect(Collectors.toList()));
SecurityContextHolder.getContext().setAuthentication(auth);
}
} catch (Exception e) {
SecurityContextHolder.clearContext();
}
System.out.println(String.format("%s request to %s", request1.getMethod(), request1.getRequestURL().toString()));
/* return null;*/
request1.setAttribute("header",token);
chain.doFilter(request1, response);
}
}
In your application.properties of zuul you should add zuul.sensitiveHeaders=Cookie,Set-Cookie

How to gain access to remote ejb used in main application WAR from REST API war

I have some legacy JEE application and REST API module (both are WAR packaged) implemented with use of JAX-RS that was introduced as a POC in the past. Now I need to somewhat make these two to talk to each other.
All WARs are then deployed on Apache TomEE.
For instance, I have such situation
Endpoint class
#Path("/somepath")
#Stateless
public class Endpoint {
#EJB
private ServiceBean bean;
#Since(CommonParams.VERSION_1)
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response getSomeContent(#Context UriInfo uriInfo, #BeanParam SomeParams params) {
return getContent(uriInfo, bean.getSomeContent(), params);
}
}
Bean class
#DeclareRoles("ADMIN")
#Stateless
#Remote(SomeService.class)
#Local(SomeServiceLocal.class)
public class ServiceBean {
// Methods with #RolesAllowed("ADMIN") annotations
}
The problem is that whenever I try to invoke GET endpoint TomEE replies with such exception:
javax.ejb.EJBAccessException: Unauthorized Access by Principal Denied
I've tried to implement some Servlet Filter for managing the authentication (BasicAuth), but even though I'm able to authenticate that way, the error mentioned above still persists.
Auth Filter Class
#WebFilter(
urlPatterns = "/*",
initParams = {
#WebInitParam(name = "realm", value = "realm")
})
public class AuthFilter {
private String realm = "realm";
private InitialContext context;
#Override
public void init(FilterConfig filterConfig) throws ServletException {
String paramRealm = filterConfig.getInitParameter("realm");
if (!Strings.isNullOrEmpty(paramRealm)) {
realm = paramRealm;
}
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String authHeader = request.getHeader("Authorization");
if (authHeader != null) {
StringTokenizer st = new StringTokenizer(authHeader);
if (st.hasMoreTokens()) {
String basic = st.nextToken();
if (basic.equalsIgnoreCase("Basic")) {
try {
String credentials = new String(
Base64.decodeBase64(st.nextToken()), "UTF-8");
log.info("Credentials: " + credentials);
int p = credentials.indexOf(":");
if (p != -1) {
String _username = credentials.substring(0, p).trim();
String _password = credentials.substring(p + 1).trim();
Properties props = new Properties();
props.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.RemoteInitialContextFactory");
props.setProperty(Context.PROVIDER_URL, "ejbd://localhost:4201");
props.setProperty(Context.SECURITY_PRINCIPAL, _username);
props.setProperty(Context.SECURITY_CREDENTIALS, _password);
props.setProperty("openejb.authentication.realmName", realm);
try {
getServletContext();
context = new InitialContext(props);
} catch (NamingException e) {
e.printStackTrace();
}
filterChain.doFilter(servletRequest, servletResponse);
} else {
unauthorized(response, "Invalid authentication token");
}
} catch (UnsupportedEncodingException e) {
throw new Error("Couldn't retrieve authentication", e);
}
}
}
} else {
unauthorized(response);
}
}
#Override
public void destroy() {
}
private void unauthorized(HttpServletResponse response, String message) throws IOException {
response.setHeader("WWW-Authenticate", "Basic realm=\"" + realm + "\"");
response.sendError(401, message);
}
private void unauthorized(HttpServletResponse response) throws IOException {
unauthorized(response, "Unauthorized");
}
}
Do You guys have any clue what could be wrong with that? Am I missing somenting?

Spring OAuth2.0: Getting User Roles based on Client Id

I have multiple clients registered for my oauth2 auth server. lets say user1 have roles such as ROLE_A, ROLE_B for client1, same user has roes such as ROLE_C, ROLE_D for client2. now when the user logins either using client1 or client2 he is able to see all the four roles ie. ROLE_A, ROLE_B, ROLE_C and ROLE_D.
My requirement was when the user1 logins to client1 it should return only the roles ROLE_A and ROLE_B. when he logins using client2 it should return only ROLE_C and ROLE_D
For achieving this, what I planned is within the authenticate function, I need to get the clientId. so using the clientId and the username I can find the corresponding roles allocated to the user from the db (client-user-roles-mapping table). .But the issue is I don't know how to get the clientId within the authenticate function
#Override
public Authentication authenticate(final Authentication authentication) throws AuthenticationException {
String userName = ((String) authentication.getPrincipal()).toLowerCase();
String password = (String) authentication.getCredentials();
if (userName != null && authentication.getCredentials() != null) {
String clientId = // HERE HOW TO GET THE CLIENT ID
Set<String> userRoles = authRepository.getUserRoleDetails(userName.toLowerCase(), clientId);
Collection<SimpleGrantedAuthority> authorities = fillUserAuthorities(userRoles);
Authentication token = new UsernamePasswordAuthenticationToken(userName, StringUtils.EMPTY, authorities);
return token;
} else {
throw new BadCredentialsException("Authentication Failed!!!");
}
} else {
throw new BadCredentialsException("Username or Password cannot be empty!!!");
}
}
Can anyone please help me on this
UPDATE 1
CustomAuthenticationProvider.java
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
private final Logger log = LoggerFactory.getLogger(getClass());
#Autowired
private LDAPAuthenticationProvider ldapAuthentication;
#Autowired
private AuthRepository authRepository;
public CustomAuthenticationProvider() {
super();
}
#Override
public Authentication authenticate(final Authentication authentication) throws AuthenticationException {
String userName = ((String) authentication.getPrincipal()).toLowerCase();
String password = (String) authentication.getCredentials();
if (userName != null && authentication.getCredentials() != null) {
String clientId = // HERE HOW TO GET THE CLIENT ID
Set<String> userRoles = authRepository.getUserRoleDetails(userName.toLowerCase(), clientId);
Collection<SimpleGrantedAuthority> authorities = fillUserAuthorities(userRoles);
Authentication token = new UsernamePasswordAuthenticationToken(userName, StringUtils.EMPTY, authorities);
return token;
} else {
throw new BadCredentialsException("Authentication Failed!!!");
}
} else {
throw new BadCredentialsException("Username or Password cannot be empty!!!");
}
}
public boolean invokeAuthentication(String username, String password, Boolean isClientValidation) {
try {
Map<String, Object> userDetails = ldapAuthentication.authenticateUser(username, password);
if(Boolean.parseBoolean(userDetails.get("success").toString())) {
return true;
}
} catch (Exception exception) {
log.error("Exception in invokeAuthentication::: " + exception.getMessage());
}
return false;
}
#Override
public boolean supports(Class<? extends Object> authentication) {
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}
private Collection<SimpleGrantedAuthority> fillUserAuthorities(Set<String> roles) {
Collection<SimpleGrantedAuthority> authorties = new ArrayList<SimpleGrantedAuthority>();
for(String role : roles) {
authorties.add(new SimpleGrantedAuthority(role));
}
return authorties;
}
}
Here is you code after modification
#Override
public Authentication authenticate(final Authentication authentication) throws AuthenticationException {
String userName = ((String) authentication.getPrincipal()).toLowerCase();
String password = (String) authentication.getCredentials();
if (userName != null && authentication.getCredentials() != null) {
String clientId = getClientId();
// validate client ID before use
Set<String> userRoles = authRepository.getUserRoleDetails(userName.toLowerCase(), clientId);
Collection<SimpleGrantedAuthority> authorities = fillUserAuthorities(userRoles);
Authentication token = new UsernamePasswordAuthenticationToken(userName, StringUtils.EMPTY, authorities);
return token;
} else {
throw new BadCredentialsException("Authentication Failed!!!");
}
} else {
throw new BadCredentialsException("Username or Password cannot be empty!!!");
}
private String getClientId(){
final HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
final String authorizationHeaderValue = request.getHeader("Authorization");
final String base64AuthorizationHeader = Optional.ofNullable(authorizationHeaderValue)
.map(headerValue->headerValue.substring("Basic ".length())).orElse("");
if(StringUtils.isNotEmpty(base64AuthorizationHeader)){
String decodedAuthorizationHeader = new String(Base64.getDecoder().decode(base64AuthorizationHeader), Charset.forName("UTF-8"));
return decodedAuthorizationHeader.split(":")[0];
}
return "";
}
more info about RequestContextHolder
Extend UsernamePasswordAuthenticationToken
A POJO is required to hold not just the username and the password but also the client identifier.
public ExtendedUsernamePasswordAuthenticationToken extends UsernamePasswordAuthenticationToken {
private final String clientId;
public ExtendedUsernamePasswordAuthenticationToken(Object principal
, Object credentials
, String clientId) {
super(principal, credentials);
this.clientId = clientId;
}
public String getClientId() { return clientId; }
}
Extend UsernamePasswordAuthenticationFilter
The authentication process needs to be tweaked so that the client identifier is passed to the authentication code in addition to the username and password.
public class ExtendedUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
public ExtendedUsernamePasswordAuthenticationFilter () { super(); }
#Override
public public Authentication attemptAuthentication(HttpServletRequest request
, HttpServletResponse response)
throws AuthenticationException {
// See the source code of UsernamePasswordAuthenticationFilter
// to implement this. Instead of creating an instance of
// UsernamePasswordAuthenticationToken, create an instance of
// ExtendedUsernamePasswordAuthenticationToken, something along
// the lines of:
final String username = obtainUsername(request);
final String password = obtainPassword(request);
final String clientId = obtainClientId(request);
...
final Authentication authentication = new ExtendedUsernamePasswordAuthenticationToken(username, password, clientId);
return getAuthenticationManager().authenticate(authentication);
}
}
Use the extra information available for logging in
public CustomAuthenticationProvider implements AuthenticationProvider {
...
#Override
public boolean supports(final Class<?> authentication) {
return authentication.isAssignableFrom(ExtendedUsernamePasswordAuthenticationToken.class);
}
#Override
public Authentication authenticate(final Authentication authentication)
throws AuthenticationException {
}
}
Force Spring Security to use the custom filter
<bean class="com.path.to.filter.ExtendedUsernamePasswordAuthenticationFilter" id="formAuthenticationFilter">
<property name="authenticationManager" ref="authenticationManager"/>
</bean>
<http ... >
<security:custom-filter position="FORM_LOGIN_FILTER" ref="formAuthenticationFilter"/>
...
</http>
or, if using Java configuration:
#Bean
public ExtendedUsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter(final AuthenticationManager authenticationManager) {
final ExtendedUsernamePasswordAuthenticationFilter filter = new ExtendedUsernamePasswordAuthenticationFilter();
filter.setAuthenticationManager(authenticationManager);
return filter;
}
protected void configure(HttpSecurity http) throws Exception {
http.addFilterAt(usernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
...
}
For your requirement, since you want to just access additional parameters from the request, you could try out the following in your CustomAuthenticationProvider class
#Autowired
private HttpServletRequest request;
Add the following logic to read the httpRequest parameters and add your logic to access the authorization key
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Enumeration<String> headerNames = request.getHeaderNames();
while(headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
System.out.println("Header Name - " + headerName + ", Value - " + request.getHeader(headerName));
}
}
Now, you will have the encode Basic Authentication field which you can decode like the one below
if (authorization != null && authorization.startsWith("Basic")) {
// Authorization: Basic base64credentials
String base64Credentials = authorization.substring("Basic".length()).trim();
String credentials = new String(Base64.getDecoder().decode(base64Credentials),
Charset.forName("UTF-8"));
// client/secret = clientId:secret
final String[] values = credentials.split(":",2);

Categories

Resources