I've got problem with error "Unauthorized" in my app. I'm using Spring Security and oauth2. My Clients and Users are store in database. I have got error 401 in PostMan when I start to use Client from database. Client is saving in database but still I've got error when I want to get token access from localhost:8080/oauth/token. Below is my source :
AuthorizationServerConfig :
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private TokenStore tokenStore;
private CustomClientDetailsService customClientDetailsService;
#Bean
PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(customClientDetailsService);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenStore(tokenStore)
.authenticationManager(authenticationManager);
}
}
This is my CustomClientDetails :
public class CustomClientDetails implements ClientDetails {
final static Logger log = LoggerFactory.getLogger(CustomClientDetailsService.class);
private static final long serialVersionUID = 6602529451366778198L;
private Clients clients;
public CustomClientDetails(final Clients clients){
this.clients = clients;
}
#Override
public String getClientId() {
return clients.getClientId();
}
#Override
public Set<String> getResourceIds() {
final Set<String> resourcesIds = new HashSet<String>();
resourcesIds.add(clients.getResourceIds());
return resourcesIds;
}
#Override
public boolean isSecretRequired() {
return true;
}
#Override
public String getClientSecret() {
return clients.getClientSecret();
}
#Override
public boolean isScoped() {
return true;
}
#Override
public Set<String> getScope() {
final Set<String> scopes = new HashSet<String>();
scopes.add(clients.getScope());
return scopes;
}
#Override
public Set<String> getAuthorizedGrantTypes() {
final Set<String> authorizedGrantTypes = new HashSet<String>();
authorizedGrantTypes.add(clients.getAuthorizedGrantTypes());
return authorizedGrantTypes;
}
#Override
public Set<String> getRegisteredRedirectUri() {
final Set<String> registeredRedirectUris = new HashSet<String>();
registeredRedirectUris.add(clients.getWebServerRedirectUri());
return registeredRedirectUris;
}
#Override
public Collection<GrantedAuthority> getAuthorities() {
final Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority(clients.getAuthorities()));
return authorities;
}
#Override
public Integer getAccessTokenValiditySeconds() {
return clients.getAccessTokenValidity();
}
#Override
public Integer getRefreshTokenValiditySeconds() {
return clients.getRefreshTokenValidity();
}
#Override
public boolean isAutoApprove(String s) {
return false;
}
#Override
public Map<String, Object> getAdditionalInformation() {
final Set<String> additionalInformation = new HashSet<String>();
additionalInformation.add(clients.getAdditionalInformation());
return null;
}
This is a CustomClientDetailsService :
public class CustomClientDetailsService implements ClientDetailsService {
#Autowired
private ClientsRepository clientsRepository;
#Autowired
private CustomClientDetails customClientDetails;
#Override
public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {
Clients client = clientsRepository.findByClientId(clientId);
final CustomClientDetails customClientDetails = new CustomClientDetails(client);
return customClientDetails;
}
And error from PostMan :
{
"timestamp": "2019-02-20T09:32:15.479+0000",
"status": 401,
"error": "Unauthorized",
"message": "Unauthorized",
"path": "/oauth/token"
}
You should provide a client_id and a client_secret in postman, in the authorization section, you can set a Basic Auth.
In the username field, put your client_id and in the password, put your client_secret.
"Unauthorized" at "/oauth/token" probably means you didn't provide HTTP Basic Auth credentials in request headers. As far as I recall this endpoint is secured by default with login and password stored in oauth_client_details entity. Look for client_id + client_secret pair and provide it to Postman with Authorization->Basic Auth settings.
Related
I have a Web Filter that sets an object in a ThreadLocal attribute and I'm trying to understand how/when this Thread local should be cleaned-up (ThreadLocal.remove()) to avoid the exception "User context already initiated." that happens because it is being retrieved from the Spring Boot Thread Pool with the previous values set.
I'm using Spring Webflux.
Where can I hook this SecurityAuthorizationContext.clean() call?
public class SecurityAuthorizationContext
{
private static final ThreadLocal<PrivilegeHolder> userContext = new ThreadLocal<>();
private final List<String> roles;
private SecurityAuthorizationContext(List<String> roles)
{
this.roles = roles;
}
public static void create(List<String> roles)
{
if (nonNull(userContext.get()))
{
log.error("User context already initiated.");
throw new AuthorizationException("User context already initiated.");
}
PrivilegeHolder privilegeHolder = new PrivilegeHolder();
userContext.set(privilegeHolder);
// example of privileges retrieved from database by the user roles
privilegeHolder.add(INSERT);
privilegeHolder.add(DELETE);
}
public static void clean()
{
userContext.remove();
}
public static boolean hasInsertPrivilege()
{
return userContext.get().hasPrivilege(INSERT);
}
public static boolean hasDeletePrivilege()
{
return userContext.get().hasPrivilege(DELETE);
}
}
public class AuthorizationFilter implements OrderedWebFilter
{
private static final String USER_ROLES = "user-roles";
#Override
public int getOrder()
{
return SecurityWebFiltersOrder.AUTHORIZATION.getOrder();
}
#Override
public Mono<Void> filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain)
{
ServerHttpRequest request = serverWebExchange.getRequest();
HttpHeaders headers = request.getHeaders();
List<String> roles = headers.get(USER_ROLES);
SecurityAuthorizationContext.create(roles);
return webFilterChain.filter(serverWebExchange);
}
}
#Configuration
#EnableWebFluxSecurity
#EnableTransactionManagement
public class ApplicationConfiguration
{
#Autowired
private AuthorizationFilter authorizationFilter;
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http)
{
return http
.csrf().disable()
.authorizeExchange()
.pathMatchers("/**").permitAll()
.and()
.addFilterAt(authorizationFilter, AUTHORIZATION)
.build();
}
}
UPDATE: Long story short ... I just want to extract something from request headers and make it available to all the stack without passing it as parameter.
So, better to use reactor context instead of ThreadLocal, here you can read about: https://projectreactor.io/docs/core/release/reference/#context
I'm creating an API with Spring Boot and Spring Security. I already created some basic authentication mechanism. And currently facing some unknown problem with authorization of requests.
Here is my Configuration class:
// removed for brevity
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final CustomUserDetailsService customUserDetailsService;
private final JwtTokenFilter jwtTokenFilter;
private final CustomAuthenticationProvider customAuthenticationProvider;
public SecurityConfiguration(CustomUserDetailsService customUserDetailsService,
JwtTokenFilter jwtTokenFilter,
CustomAuthenticationProvider customAuthenticationProvider) {
this.customUserDetailsService = customUserDetailsService;
this.jwtTokenFilter = jwtTokenFilter;
this.customAuthenticationProvider = customAuthenticationProvider;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// todo: provide an authenticationProvider for authenticationManager
/* todo:
In most use cases authenticationProvider extract user info from database.
To accomplish that, we need to implement userDetailsService (functional interface).
Here username is an email.
* */
auth.userDetailsService(customUserDetailsService);
auth.authenticationProvider(customAuthenticationProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// Enable CORS and disable CSRF
http = http.cors().and().csrf().disable();
// Set session management to Stateless
http = http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and();
// Set unauthorized requests exception handler
http = http
.exceptionHandling()
.authenticationEntryPoint(
(request, response, ex) -> {
response.sendError(
HttpServletResponse.SC_UNAUTHORIZED,
ex.getMessage()
);
}
)
.and();
// Set permissions and endpoints
http.authorizeRequests()
.antMatchers("/api/v1/auth/**").permitAll()
.antMatchers("/api/v1/beats/**").hasRole("ADMIN")
.anyRequest().authenticated();
http.addFilterBefore(jwtTokenFilter,
UsernamePasswordAuthenticationFilter.class);
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// Used by spring security if CORS is enabled.
#Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source =
new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
#Override #Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults(""); // Remove the ROLE_ prefix
}
}
To check if user has rights to access resource, I use info from JWT payload. To do so I have a filter class:
// removed for brevity
#Component
public class JwtTokenFilter extends OncePerRequestFilter {
private final static Logger logger = LoggerFactory.getLogger(JwtTokenFilter.class);
private final JwtTokenUtil jwtTokenUtil;
private final CustomUserDetailsService customUserDetailsService;
public JwtTokenFilter(JwtTokenUtil jwtTokenUtil,
CustomUserDetailsService customUserDetailsService) {
this.jwtTokenUtil = jwtTokenUtil;
this.customUserDetailsService = customUserDetailsService;
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
final String header = request.getHeader(HttpHeaders.AUTHORIZATION);
if (header == null || header.isEmpty() || !header.startsWith("Bearer ")) {
logger.error("Authorization header missing");
filterChain.doFilter(request, response);
return;
}
final String token = header.split(" ")[1].trim();
if (!jwtTokenUtil.validate(token)) {
filterChain.doFilter(request, response);
return;
}
UserDetails userDetails = customUserDetailsService.loadUserByUsername(token);
if (userDetails == null)
throw new ServletException("Couldn't extract user from JWT credentials");
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, userDetails.getPassword(), userDetails.getAuthorities());
authentication.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
}
}
To represent UserDetails, I've implemented CustomUserDetails and CustomUserDetailsService classes:
#Data
#NoArgsConstructor
public class CustomUserDetails implements UserDetails {
private Long userId;
private Long profileId;
private String email;
private String password;
private String fullName;
private String nickname;
private String avatar;
private String phoneNumber;
private ProfileState profileState;
private Collection<? extends GrantedAuthority> grantedAuthorities;
public static CustomUserDetails fromUserAndProfileToMyUserDetails(Profile profile) {
CustomUserDetails customUserDetails = new CustomUserDetails();
customUserDetails.setUserId(profile.getUser().getId());
customUserDetails.setEmail(profile.getUser().getEmail());
customUserDetails.setPassword(profile.getUser().getPassword());
customUserDetails.setProfileId(profile.getId());
customUserDetails.setFullName(profile.getFullName());
customUserDetails.setNickname(profile.getNickname());
customUserDetails.setAvatar(profile.getAvatar());
customUserDetails.setPhoneNumber(profile.getPhoneNumber());
customUserDetails.setProfileState(profile.getState());
return customUserDetails;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return grantedAuthorities;
}
#Override
public String getPassword() {
return password;
}
#Override
public String getUsername() {
return nickname;
}
#Override
public boolean isAccountNonExpired() {
return false;
}
#Override
public boolean isAccountNonLocked() {
return false;
}
#Override
public boolean isCredentialsNonExpired() {
return false;
}
#Override
public boolean isEnabled() {
return false;
}
}
CustomUserDetailsService.java:
#Component
public class CustomUserDetailsService implements UserDetailsService {
private Logger logger = LoggerFactory.getLogger(CustomUserDetailsService.class);
private final ProfileRepository profileRepository;
private final JwtTokenUtil jwtTokenUtil;
public CustomUserDetailsService(ProfileRepository profileRepository, JwtTokenUtil jwtTokenUtil) {
this.profileRepository = profileRepository;
this.jwtTokenUtil = jwtTokenUtil;
}
#Override
public UserDetails loadUserByUsername(String token) throws UsernameNotFoundException {
if (token == null || token.isEmpty()) throw new IllegalArgumentException("Token cannot be null or empty");
try {
final String nickname = jwtTokenUtil.getNickname(token);
Profile profile = profileRepository
.findByNickname(nickname)
.orElseThrow(() -> new UsernameNotFoundException(
String.format("User: %s not found", token)
));
logger.info(String.format("Extracted Profile: %s", profile));
CustomUserDetails customUserDetails = CustomUserDetails.fromUserAndProfileToMyUserDetails(profile);
List<GrantedAuthority> authorities = new ArrayList<>(Collections.emptyList());
authorities.add(new SimpleGrantedAuthority(profile.getType().getValue()));
customUserDetails.setGrantedAuthorities(authorities);
return customUserDetails;
} catch (Exception e) {
logger.error("Wasn't able to load user `{}`. Exception occurred `{}`", token, e.getMessage());
return null;
}
}
}
Here is the controller that I want to access:
#RestController
#RequestMapping("/api/beats")
public class BeatController {
private static final Logger logger = LogManager.getLogger(BeatController.class);
private final BeatService beatService;
public BeatController(BeatService beatService) {
this.beatService = beatService;
}
#GetMapping("{id}")
public Object getBeat(#PathVariable Long id) {
try {
return beatService.findById(id);
} catch (Exception e) {
logger.error("Can't find beat with id " + id);
return new ResponseEntity<>(new DefaultResponseDto("failed", e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
#GetMapping
public Object getBeats(#RequestParam String filter, #RequestParam String page) {
try {
return beatService.findAll();
} catch (Exception e) {
logger.error("Can't find beats");
return new ResponseEntity<>(new DefaultResponseDto("failed", e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
#PostMapping
public Object createBeat(#RequestBody BeatDto beatDto) {
try {
beatDto.setId(null);
return beatService.save(beatDto);
} catch (Exception e) {
logger.error("Can't create new Beat");
return new ResponseEntity<>(new DefaultResponseDto("failed", e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
#PutMapping("{id}")
public Object updateBeat(#PathVariable Long id, #RequestBody BeatDto newBeat) {
try{
BeatDto oldBeat = beatService.findById(id);
if (oldBeat != null) {
newBeat.setId(id);
} else {
throw new Exception();
}
return beatService.save(newBeat);
} catch (Exception e) {
return new ResponseEntity<>(new DefaultResponseDto("failed", e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
#DeleteMapping("{id}")
public Object deleteBeat(#PathVariable Long id) {
try {
return beatService.deleteById(id);
} catch (Exception e) {
return new ResponseEntity<>(new DefaultResponseDto("failed", e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
So, I make a request, provide it with and correct Authorization header and access token. It gets a user from DB and fetches GrantedAuthority. Last steps are:
It sets authentication object in SecurityContext.
Goes further in FilterChain.
But it doens't reach controller, and it doens't throw any exceptions. Only responses me with 403. May be I forgot something to setup, or problem might be somewehere else? Guide me please.
So finally figured out what was the problem. Main advices that helped me here:
All methods in CustomUserDetails service that were returning false return true. (Advice from M. Deinum)
Turned on spring framework security logs with: logging.level.org.springframework.security=TRACE.
This helped me to trace an exception, that FilterChain was throwing.
Thanks to Marcus Hert da Coregio.
What I changed to fix a problem? First I updated #RequestMapping mismatch in BeatController. Stack trace showed me that while it was properly fetching user Role from DB, it failed to match my Role and the one I wrote in Configuration class. By default, it add "ROLE_" prefix before the actual role name we provide. I thought that defining this bean changes this behavior:
GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults(""); // Remove the ROLE_ prefix
}
Turns out that it doesn't effect to prefixing behavior, so it was adding "ROLE_" before the "ADMIN" role name I provided. Adding "ROLE_" prefix while authenticating request fixed problem:
FROM
authorities.add(new SimpleGrantedAuthority(profile.getType().getValue()));
TO
authorities.add(new SimpleGrantedAuthority("ROLE_" + profile.getType().getValue()));
Additionally I cleaned build and rebuild the project with gradle. Thanks to all people that helped!
I'm pretty new to Spring-Boot. I have tried to block certain routes, permit some and implement authentication for them. This works so far, but somehow I want to get the user who makes the request.
My WebSecurityConfig:
#Configuration
#Order(1)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final JwtTokenService tokenService;
public WebSecurityConfig(JwtTokenService tokenService) {
this.tokenService = tokenService;
}
#Bean
public JwtTokenFilter tokenFilter() {
return new JwtTokenFilter();
}
#Bean
public SecurityContextHolderAwareRequestFilter securityContextHolderAwareRequestFilter() {
return new SecurityContextHolderAwareRequestFilter();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers("/login", "/apply").permitAll()
.anyRequest().authenticated();
http.addFilterBefore(tokenFilter(), UsernamePasswordAuthenticationFilter.class);
http.headers().cacheControl();
}
#Autowired
public void configureAuthentication(AuthenticationManagerBuilder builder) {
builder.authenticationProvider(new AuthenticationProvider() {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String token = (String) authentication.getCredentials();
return tokenService.decode(token).map(AuthenticatedUser::new).orElseThrow(JwtAuthenticationException::new);
}
#Override
public boolean supports(Class<?> authentication) {
return JwtAuthentication.class.equals(authentication);
}
});
}
}
The TokenFilter:
#Component
public class JwtTokenFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
String header = request.getHeader("Anima-Authentication-Token");
if(header != null) {
SecurityContextHolder.getContext().setAuthentication(new JwtAuthentication(header));
}
filterChain.doFilter(request, response);
}
}
The Authentications:
public class JwtAuthentication implements Authentication {
private final String token;
public JwtAuthentication(String token) {
this.token = token;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
#Override
public Object getCredentials() {
return token;
}
#Override
public Object getDetails() {
return null;
}
#Override
public Object getPrincipal() {
return null;
}
#Override
public boolean isAuthenticated() {
return false;
}
#Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
}
#Override
public String getName() {
return null;
}
}
public class AuthenticatedUser implements Authentication {
private final AnimaUser user;
public AuthenticatedUser(AnimaUser user) {
this.user = user;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return new ArrayList<>();
}
#Override
public Object getCredentials() {
return null;
}
#Override
public Object getDetails() {
return null;
}
#Override
public Object getPrincipal() {
return user;
}
#Override
public boolean isAuthenticated() {
return true;
}
#Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
}
#Override
public String getName() {
return user.getName();
}
}
I also tried to override the Authentication in the SecurityContextHolder with the AuthorizedUser Object:
#Autowired
public void configureAuthentication(AuthenticationManagerBuilder builder) {
builder.authenticationProvider(new AuthenticationProvider() {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String token = (String) authentication.getCredentials();
return tokenService.decode(token).map(user - > {
SecurityContextHolder.getContext().setAuthentication(new AuthenticatedUser(user));
return new AuthenticatedUser(user);
}).orElseThrow(JwtAuthenticationException::new);
}
#Override
public boolean supports(Class<?> authentication) {
return JwtAuthentication.class.equals(authentication);
}
});
}
But this hasn't worked either. I have tried to access the User with the following methods:
#GetMapping("")
#ResponseBody
public String handle() {
// This returns the JwtAuthentication, not the AuthenticatedUser
return SecurityContextHolder.getContext().getAuthentication().getCredentials().toString();
}
#GetMapping("")
#ResponseBody
public String handle(Authentication authentication) {
// authentication is null
}
#GetMapping("")
#ResponseBody
public String handle(Principle principle) {
// principle is null
}
It is because of #Component annotation on JwtTokenFilter class. Remove that and you will be good to go. You are already defining that as a #Bean in your WebSecurityConfig class. Since you have #Component on the class it is running after the AuthenticationProvider code overriding the AuthenticatedUser set in SecurityContext with JwtAuthentication
What I'm trying to implement is to make my multitenant app workspace aware. With this I mean that besides username and password, I'm validating the workspace as well.
Before, I had (working) normal authentication (username and password) and a JWTFilter that is a OncePerRequestFilter.
What I did?
Extended UsernamePasswordAuthenticationToken: just to add the workspace
Extended AbstractUserDetailsAuthenticationProvider: defining my customPasswordEncoder and customUserDetailsService
Made a CustomUserDetailsService: instead of the loadByUsername I've made a loadUserByWorkspaceAndUsername
Configured the WebSecurity for the new extended classes
The outcome is always unauthorized :(
What I've tried?
While debugging the code never pass on the CustomAuthenticationFilter and that's the reason I'm focusing my efforts there. Really doesn't know what I'm doing wrong here. If you need any further information please shout.
Replacing the UsernamePasswordAuthenticationFilter using the addFilter(authenticationFilter())
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterAfter(authenticationFilter(), JwtFilter.class);
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(authenticationFilter(),UsernamePasswordAuthenticationFilter.class);
.addFilterBefore(authenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(jwtAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class);
A bit of code.
CustomAuthenticationToken
public class CustomAuthenticationToken extends UsernamePasswordAuthenticationToken {
private String workspace;
public CustomAuthenticationToken(final Object principal,
final Object credentials,
final String workspace) {
super(principal, credentials);
this.workspace = workspace;
}
public CustomAuthenticationToken(final Object principal,
final Object credentials,
final String workspace, Collection<? extends GrantedAuthority> authorities) {
super(principal, credentials, authorities);
this.workspace = workspace;
super.setAuthenticated(true);
}
public String getWorkspace() {
return this.workspace;
}
}
CustomAuthenticationFilter
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private static final String SPRING_SECURITY_FORM_DOMAIN_KEY = "workspace";
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
if (!request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: "
+ request.getMethod());
}
CustomAuthenticationToken authRequest = getAuthRequest(request);
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
private CustomAuthenticationToken getAuthRequest(HttpServletRequest request) {
String username = obtainUsername(request);
String password = obtainPassword(request);
String domain = obtainDomain(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
if (domain == null) {
domain = "";
}
username = username.trim();
return new CustomAuthenticationToken(username, password, domain);
}
private String obtainDomain(HttpServletRequest request) {
return request.getParameter(SPRING_SECURITY_FORM_DOMAIN_KEY);
}
}
CustomUserDetailsAuthenticationProvider
#Component
public class CustomUserDetailsAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
/**
* The plaintext password used to perform
* PasswordEncoder#matches(CharSequence, String)} on when the user is
* not found to avoid SEC-2056.
*/
private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
private final PasswordEncoder customPasswordEncoder;
private final CustomUserDetailsService customUserDetailsService;
private String userNotFoundEncodedPassword;
public CustomUserDetailsAuthenticationProvider(final PasswordEncoder customPasswordEncoder,
final CustomUserDetailsService customUserDetailsService) {
this.customPasswordEncoder = customPasswordEncoder;
this.customUserDetailsService = customUserDetailsService;
}
#Override
protected void additionalAuthenticationChecks(final UserDetails userDetails,
final UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
if (authentication.getCredentials() == null) {
logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
final String presentedPassword = authentication.getCredentials().toString();
if (!customPasswordEncoder.matches(presentedPassword, userDetails.getPassword())) {
logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
#Override
protected UserDetails retrieveUser(final String username,
final UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
final CustomAuthenticationToken auth = (CustomAuthenticationToken) authentication;
UserDetails loadedUser;
try {
loadedUser = this.customUserDetailsService.loadUserByWorkspaceAndUsername(auth.getWorkspace(), auth.getPrincipal().toString());
} catch (UsernameNotFoundException notFound) {
if (authentication.getCredentials() != null) {
String presentedPassword = authentication.getCredentials().toString();
customPasswordEncoder.matches(presentedPassword, userNotFoundEncodedPassword);
}
throw notFound;
} catch (Exception repositoryProblem) {
throw new InternalAuthenticationServiceException(repositoryProblem.getMessage(), repositoryProblem);
}
if (loadedUser == null) {
throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}
#Override
protected void doAfterPropertiesSet() throws Exception {
Assert.notNull(this.customUserDetailsService, "A UserDetailsService must be set");
this.userNotFoundEncodedPassword = this.customPasswordEncoder.encode(USER_NOT_FOUND_PASSWORD);
}
}
CustomUserDetailsServiceImpl
#Component
public class CustomUserDetailsServiceImpl implements CustomUserDetailsService {
private static final Logger LOGGER = LoggerFactory.getLogger(com.cliwise.security.workspace.CustomUserDetailsServiceImpl.class);
private final LoginAttemptService loginAttemptService;
private final UserRepository userRepository;
private final HttpServletRequest request;
public CustomUserDetailsServiceImpl(LoginAttemptService loginAttemptService, UserRepository userRepository, HttpServletRequest request) {
this.loginAttemptService = loginAttemptService;
this.userRepository = userRepository;
this.request = request;
}
#Override
public UserDetails loadUserByWorkspaceAndUsername(String workspace, String username) throws UsernameNotFoundException {
final User user = userRepository.findByUsernameOrEmailAndWorkspace(username, username, workspace)
.orElseThrow(() -> new UserNotFoundException("User not found with username or email : " + username));
return UserPrincipal.create(user);
}
}
The last but no least
WebSecurity
#EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
private final CustomAuthenticationEntryPoint unauthorizedHandler;
private final CustomUserDetailsAuthenticationProvider customUserDetailsAuthenticationProvider;
public WebSecurity(final CustomAuthenticationEntryPoint unauthorizedHandler,
final CustomUserDetailsAuthenticationProvider customUserDetailsAuthenticationProvider) {
this.unauthorizedHandler = unauthorizedHandler;
this.customUserDetailsAuthenticationProvider = customUserDetailsAuthenticationProvider;
}
#Override
public void configure(final AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.authenticationProvider(customUserDetailsAuthenticationProvider);
}
#Bean
public CustomAuthenticationFilter authenticationFilter() throws Exception {
CustomAuthenticationFilter filter = new CustomAuthenticationFilter();
filter.setAuthenticationManager(authenticationManagerBean());
return filter;
}
#Bean(BeanIds.AUTHENTICATION_MANAGER)
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.httpBasic().disable()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler)
.and()
.exceptionHandling().accessDeniedHandler(accessDeniedHandler())
.and()
.addFilterBefore(jwtAuthenticationFilter(), CustomAuthenticationFilter.class);
http
.authorizeRequests()
.antMatchers("/auth").permitAll()
.anyRequest()
.authenticated();
}
#Bean
public AccessDeniedHandler accessDeniedHandler() {
return new CustomAccessDeniedHandler();
}
#Bean
public JwtFilter jwtAuthenticationFilter() {
return new JwtFilter();
}
}
Thanks in advance for your time.
My understanding is that you are facing the problem in CustomUserDetailsAuthenticationProvider. Since you are extending AbstractUserDetailsAuthenticationProver you will get a default implementation for
public Authentication authenticate(Authentication authentication)
throws AuthenticationException;
See if its properly authenticating the authentication object, if not you will have to override the method and write your own implementation.
I have a spring boot application i have many endpoint in this application. When i am hitting endpoint simultaneously JSON response from two different endpoint got interchanged.
For example:
i am hitting /currency/list endpoint and /fee endpoint and i am getting /fee endpoint data in currency/list endpoint and vice versa.
I have no idea why this happening. If anybody can suggest why happening will be helpful.
Also I am using spring security token based auth in this project
CurrencyController.java
#RestController
#RequestMapping(value = UrlConstant.BASE_ADMIN_URI_V1)
#Api(value = "Admin Controller")
#Scope("request")
public class CurrencyController {
public static final Logger logger = LoggerFactory.getLogger(CurrencyController.class);
#Autowired
private LocaleService localService;
#RequestMapping(value = UrlConstant.CURRENCY_LIST_FOR_MARKET, method = RequestMethod.GET)
public ResponseEntity<Object> getCurrencyListForMarket() {
List<Currency> currencyList = currencyService.getCurrencyListForMarket();
ObjectMapper mapper = new ObjectMapper();
try {
String stringList = mapper.writeValueAsString(currencyList);
logger.debug("all currency list as String: {}", stringList);
} catch (JsonProcessingException e) {
logger.debug("error in currency list: {}", e.getMessage());
e.printStackTrace();
}
return ResponseHandler.response(HttpStatus.OK, false, localService.getMessage("currency.list.success"),
currencyList);
}
}
AdminController.java
#RestController
#RequestMapping(value = UrlConstant.BASE_ADMIN_URI_V1)
#Api(value = "Admin Controller")
#Scope("request")
public class AdminController {
#Autowired
private LocaleService localeService;
#Autowired
private FeeService feeService;
#RequestMapping(value = UrlConstant.TRADING_FEES, method = RequestMethod.GET)
public ResponseEntity<Object> getTradingFees() {
TradingFee fee = tradingFeeService.getTradingFee();
return ResponseHandler.response(HttpStatus.OK, true,
localeService.getMessage("admin.transaction.fees.found.success"), fee);
}
}
TokenAuthenticationFilter.java
public class TokenAuthenticationFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
final HttpServletRequest httpRequest = (HttpServletRequest) request;
// extract token from header
String token = httpRequest.getHeader("Authorization");
if (token != null && !token.isEmpty()) {
AuthenticationTokenRepo authenticationTokenRepository = WebApplicationContextUtils
.getRequiredWebApplicationContext(httpRequest.getServletContext())
.getBean(AuthenticationTokenRepo.class);
// check whether token is valid
AuthenticationToken authToken = authenticationTokenRepository.findByToken(token);
if (authToken != null) {
// Add user to SecurityContextHolder
final UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
authToken.getUser(), null, new ApplicationUserDetail(authToken.getUser()).getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
chain.doFilter(request, response);
SecurityContextHolder.clearContext();
}
SecurityConfig.java
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private Environment environment;
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests().antMatchers(HttpMethod.POST, "/api/v1/login").permitAll()
.antMatchers(HttpMethod.POST, "/api/v1/user/register").permitAll().anyRequest().authenticated();
// Implementing Token based authentication in this filter
final TokenAuthenticationFilter tokenFilter = new TokenAuthenticationFilter();
http.addFilterBefore(tokenFilter, BasicAuthenticationFilter.class);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(UrlConstant.BASE_ADMIN_URI_V1 + UrlConstant.CURRENCY_LIST_FOR_MARKET);
web.ignoring().antMatchers(UrlConstant.BASE_ADMIN_URI_V1 + UrlConstant.TRADING_FEES);
}
}
}
ApplicationUserDetail.java
public class ApplicationUserDetail implements UserDetails,Serializable {
private static final long serialVersionUID = 1L;
transient User user;
public ApplicationUserDetail(User user) {
this.user = user;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
AuthorityUtils.commaSeparatedStringToAuthorityList(user.getRole().getName());
}
#Override
public String getPassword() {
return user.getPassword();
}
#Override
public String getUsername() {
return user.getEmailId();
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return user.getIsEnabled();
}
}
More info: When i am printing response in my api it is correct but when i am printing it in my Authentication filter i got the response of fee api in my currency api so i think there is some problem between api to filter.