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.
Related
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 want to implement my own authentication with spring. To keep things simple at first I'm going to implement the first step without any session, but an HTTP-Authorization header sent in every request.
I've read the documentation, many tutorials and of course searched on stackoverflow, but I couldn't fix it.
What I have is a filter (RequestFilter), which extracts the Authorization header and initializes the security context with an own Authentication (AuthenticationToken). Then there is an AuthenticationProvider (TokenAuthenticationProvider) supporting my AuthenticationToken. The filter is working, the authentication provider is not. None of the methods in TokenAuthenticationProvider are invoked. Can you help me, thanks :).
#EnableWebSecurity
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final Logger LOGGER = Logger.getLogger(SecurityConfig.class.getName());
#Autowired
TokenAuthenticationProvider tokenAuthenticationProvider;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(new RequestFilter(), BasicAuthenticationFilter.class)
.csrf().disable()
;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationProvider(tokenAuthenticationProvider)
;
}
}
#Component
public class TokenAuthenticationProvider implements AuthenticationProvider {
private static final Logger LOGGER = Logger.getLogger(TokenAuthenticationProvider.class.getName());
#Autowired
ClientRepository clientRepository;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
final String authToken = authentication.getCredentials().toString();
LOGGER.severe("AUTH TOKEN: " + authToken);
return Optional.ofNullable(clientRepository.findByAuthToken(authToken))
.map((Client client) -> new AuthenticationToken(client.getId(), client.getAuthToken()))
.orElseThrow(() -> new AccessDeniedException(""));
}
#Override
public boolean supports(Class<?> authentication) {
LOGGER.severe(authentication.getName());
return AuthenticationToken.class.isAssignableFrom(authentication);
}
}
public class RequestFilter extends OncePerRequestFilter {
private final Logger LOGGER = Logger.getLogger(RequestFilter.class.getName());
#Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
LOGGER.severe("RequestFilter works");
Optional.ofNullable(request.getHeader("Authorization"))
.ifPresent((String token) -> SecurityContextHolder
.getContext()
.setAuthentication(new AuthenticationToken(token))
);
chain.doFilter(request, response);
}
}
public class AuthenticationToken extends AbstractAuthenticationToken {
private final String credentials;
private final Long principal;
public AuthenticationToken(String credentials) {
this(null, credentials);
setAuthenticated(false);
}
public AuthenticationToken(Long principal, String credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
setAuthenticated(true);
}
#Override
public Object getCredentials() {
return credentials;
}
#Override
public Object getPrincipal() {
return principal;
}
}
#RestController
public class HttpGateController {
private static final Logger LOGGER = Logger.getLogger(HttpGateController.class.getName());
#RequestMapping(
name="/gate",
method= RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE
)
public String gateAction(#RequestBody String request) {
Optional.ofNullable(SecurityContextHolder.getContext())
.map(SecurityContext::getAuthentication)
.map(Authentication::getCredentials)
.map(ObjectUtils::nullSafeToString)
.ifPresent(LOGGER::severe);
return request;
}
#RequestMapping("/status")
public String statusAction() {
return "It works.";
}
}
Using Jersey 1.14 and Spring 3.1.2
I want to create a filter like this: https://gist.github.com/3031495
but in that filter I want access to a provider I created.
I'm getting an IllegalStateException. I suspect something in my lifecycle is hosed up. I can access #Context private HttpServletRequest and pull the session info I need from there, but then two classes have to know about where/how to get my "AuthUser" object.
Any help is appreciated!
My Provider:
#Component
#Provider
public class AuthUserProvider extends AbstractHttpContextInjectable<AuthUser> implements
InjectableProvider<Context, Type> {
private static final Logger LOG = LoggerFactory.getLogger(AuthUserProvider.class);
#Context
HttpServletRequest req;
public void init() {
LOG.debug("created");
}
#Override
// this may return a null AuthUser, which is what we want....remember, a
// null AuthUser means the user hasn't authenticated yet
public AuthUser getValue(HttpContext ctx) {
return (AuthUser) req.getSession().getAttribute(AuthUser.KEY);
}
// InjectableProvider implementation:
public ComponentScope getScope() {
return ComponentScope.Singleton;
}
public Injectable<AuthUser> getInjectable(ComponentContext ic, Context ctx, Type c) {
if (AuthUser.class.equals(c)) {
return this;
}
return null;
}
}
My Filter:
#Component
public class TodoFilter implements ResourceFilter {
private static final Logger LOG = LoggerFactory.getLogger(TodoFilter.class);
#Autowired
private JdbcTemplate todoTemplate;
// this works
#Context
private HttpServletRequest servletRequest;
// this throws a java.lang.IllegalStateException
// #Context
// private AuthUser authUser;
public void init() throws Exception {
LOG.debug("created");
LOG.debug(todoTemplate.getDataSource().getConnection().getMetaData()
.getDatabaseProductName());
}
#Override
public ContainerRequestFilter getRequestFilter() {
return new ContainerRequestFilter() {
#Override
public ContainerRequest filter(ContainerRequest request) {
LOG.debug("checking if {} is authorized to use {}", "my authenticated user",
request.getPath());
// String name = request.getUserPrincipal().getName();
// String[] admins = settings.getAdminUsers();
// for (String adminName : admins) {
// if (adminName.equals(name))
// return request;
// }
// if (authUser.getUsername().equals("jberk")) {
// return request;
// }
// return HTTP 403 if name is not found in admin users
throw new WebApplicationException(Response.status(Response.Status.FORBIDDEN)
.entity("You are not authorized!").build());
}
};
}
#Override
public ContainerResponseFilter getResponseFilter() {
return new ContainerResponseFilter() {
#Override
public ContainerResponse filter(ContainerRequest request,
ContainerResponse response) {
// do nothing
return response;
}
};
}
}
My Service (aka Resource):
#Component
#Path("/rs/todo")
#Produces(MediaType.APPLICATION_JSON)
#ResourceFilters(TodoFilter.class)
public class TodoService {
#GET / #POST methods
}
so I think I figured this out....
I added this to my ResourceFilter:
#Context
private HttpContext ctx;
#Autowired
private AuthUserProvider provider;
then I can do this in the filter method:
public ContainerRequest filter(ContainerRequest request) {
AuthUser authUser = provider.getValue(ctx);
// use authuser in some way
}
this might not be "correct"...but it's working and I don't have code duplication
public ComponentScope getScope() {
return ComponentScope.Singleton;
}
It should be
public ComponentScope getScope() {
return ComponentScope.PerRequest;
}