I have a Spring Boot application utilizing spring-security-jwt and spring-security-oauth2. I've got a custom User object extending UserDetails and a Custom UserDetailsService returning this object from the loadUserByUsername method.
But when I utilize the getPrincipal method of the Authentication object and try to Cast to my custom user object, it fails as the principal is returning a string vs my custom user object.
My actual goal is to eliminate the trip to the persistence layer on every method call that requires the custom object detail which is most.
You can do this by setting an AccessTokenConverter (which indirectly holds your UserDetailsService) to JwtAccessTokenConverter. See accessTokenConverter() method.
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
// Other configurations omitted
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter())
.tokenEnhancer(accessTokenConverter())
.authenticationManager(authenticationManager);
}
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
DefaultUserAuthenticationConverter duac = new DefaultUserAuthenticationConverter();
duac.setUserDetailsService(userDetailsService);
DefaultAccessTokenConverter datc = new DefaultAccessTokenConverter();
datc.setUserTokenConverter(duac);
JwtAccessTokenConverter jatc = new JwtAccessTokenConverter();
jatc.setAccessTokenConverter(datc); // IMPORTANT
jatc.setSigningKey("your-signing-key");
return jatc;
}
#Bean
public DefaultTokenServices tokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(tokenStore());
tokenServices.setSupportRefreshToken(true);
return tokenServices;
}
}
Related
I am trying to test my web api thats secured using the standard Spring Security annotations and methods.
I reviewed all the options on the site, nothing helped, here is the code. Without roles, everything works fine.I have been suffering for several days with this problem.I will be grateful for the help, thanks.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsServiceImp userDetailsService;
#Autowired
JwtFilter jwtFilter;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.POST,"/authenticate").permitAll()
.antMatchers(HttpMethod.GET,"/userData").permitAll()
.antMatchers(HttpMethod.GET,"/allUsers").hasAuthority("ROLE_ADMIN")
.anyRequest().authenticated()
.and().exceptionHandling().authenticationEntryPoint(new JwtAuthenticationEntryPoint())
.and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterAfter(jwtFilter, UsernamePasswordAuthenticationFilter.class);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
Controller class
#RestController
public class AuthenticationController {
#Autowired
public AuthenticationManager authenticationManager;
#Autowired
private UserDetailsServiceImp userDetailsService;
#Autowired
private JwtUtil jwtUtil;
#Autowired
private UserRepository userRepository;
#RequestMapping(value = "/userData", method = RequestMethod.GET)
public String hello(){
return "Hello new User";
}
#RequestMapping(value = "/allUsers", method = RequestMethod.GET)
public List<UserD> findAll(){
return userRepository.findAll();
}
#RequestMapping(value = "/authenticate", method = RequestMethod.POST)
public ResponseEntity<?> createAuthenticationToken(#RequestBody JwtRequest jwtRequest) throws Exception{
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(jwtRequest.getName(), jwtRequest.getPassword()));
}catch(BadCredentialsException e){
throw new Exception("Incorrect username and password", e);
}
final UserD userD = (UserD)userDetailsService.loadUserByUsername(jwtRequest.getName());
final String token = jwtUtil.generateToken(userD.getName(), userD.getRole());
Map<Object, Object> model = new HashMap<>();
model.put("username", jwtRequest.getName());
model.put("token", token);
return ResponseEntity.ok(model);
}
}
If antMatchers(HttpMethod.GET,"/allUsers").permitAll()
,then it returns users as it should
UserDetailsServiceImp
#Service
public class UserDetailsServiceImp implements UserDetailsService {
#Autowired
UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userRepository.findByName(username).get(0);
}
}
and User data with the addition of roles
#Component
public class DataInitializer implements CommandLineRunner {
#Autowired
UserRepository userRepository;
#Autowired
PasswordEncoder passwordEncoder;
#Override
public void run(String... args) throws Exception {
UserD user = new UserD();
user.setName("user");
user.setPassword(passwordEncoder.encode("password"));
user.setRole("ROLE_USER");
userRepository.save(user);
UserD admin = new UserD();
admin.setName("admin");
admin.setPassword(passwordEncoder.encode("password"));
admin.setRole("ROLE_ADMIN");
userRepository.save(admin);
}
}
this is what the database returns
I want to clarify something first, which may help you to pinpoint the issue:
does your UserDetailsServiceImp retrieve username and role from database or LDAP or other repository?
what role is retrieved from database or LDAP or other repository?
does it have prefix "ROLE_" already or not?
if the role retrieved from database is "ADMIN", you shall not add ROLE_ by yourself when you call hasAuthority().
.antMatchers(HttpMethod.GET,"/allUsers").hasAuthority("ADMIN")
And if it is not the case, enable the debug in the log, to see what is exactly going on with the /allUsers request.
UPDATE:
And one thing i suspected was your implementation of UserDetailsServiceImp.
You have to make sure the role was set into user details.
in your implementation, it seems that you queried out from DB directly, not sure if you set the role into the userDetails.
return userRepository.findByName(username).get(0);
Here is something it should happen:
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//assume, there is only one user with the username
UserD user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User not found with username: " + username);
}
List<GrantedAuthority> roles = new ArrayList<>();
//assume, there is only one role for the user
roles.add(new SimpleGrantedAuthority(user.getRole()));
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(),
roles);
}
I am setting up an OAuth2 + OpenID connect server using Spring security. I have been trying to use the automatic /oauth/token & /oauth/authorize endpoints that are defined when you use the #EnableAuthorizationServer annotation on a class.
#Configuration
#EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter{
In the same class, I have autowired an AuthenticationManager to use in configuring the AuthorizationServerEndpointsConfigurer. I have debugged & confirmed that the correct bean is being autowired.
#Autowired
private AuthenticationManager authMan;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception{
endpoints
.tokenStore(tokenStore())
.userApprovalHandler(userApprovalHandler())
.authenticationManager(authMan);
}
The problem is, there are two WebSecurityConfigurers being created, the one I defined and what appears to be the default WebSecurityConfigurer. Here is part of the one I defined:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
#Autowired
private UserDetailsSrvc detailsSrvc;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider
= new DaoAuthenticationProvider();
authProvider.setUserDetailsService(detailsSrvc);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
#Autowired
protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
#Override
#Bean(name="myAuthenticationManager")
public AuthenticationManager authenticationManagerBean() throws Exception {
return authenticationManager();
}
Unfortunately, the default is being called when I navigate to localhost:8080/outh/token with my browser. I can tell because my custom UserDetailsService is not being used during the authentication, and because I put a breakpoint on the getWebSecurityConfigurers method in org.springframework.security.config.annotation.web.configuration.AutowiredWebSecurityConfigurersIgnoreParents:
#SuppressWarnings({ "rawtypes", "unchecked" })
public List<SecurityConfigurer<Filter, WebSecurity>> getWebSecurityConfigurers() {
List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers = new ArrayList<SecurityConfigurer<Filter, WebSecurity>>();
Map<String, WebSecurityConfigurer> beansOfType = beanFactory
.getBeansOfType(WebSecurityConfigurer.class);
for (Entry<String, WebSecurityConfigurer> entry : beansOfType.entrySet()) {
webSecurityConfigurers.add(entry.getValue());
}
return webSecurityConfigurers;
}
The beansOfType map has two entries, but only if I have a class with the #EnableAuthorizationServer annotation. (Only 1 if I comment out annotation)
How do I get my AuthorizationServerConfigurerAdapter (or whatever is actually processing the requests to /oauth/token) to use the WebSecurityConfigurer defined in my WebSecurityConfigurerAdapter? I believe I can get around this issue by defining my own endpoints, and maybe that's the only solution, but I was hoping to utilize the default endpoints.
I having requirement of passing extra parameter along with username and password in spring security authentication process. After reading several threads I added custom authentication filter in spring security chain
below are my files
Filter class
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
String role = request.getParameter("role");
request.getSession().setAttribute("role", role);
return super.attemptAuthentication(request, response);
}
SecurityConfig class
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
RestAuthenticationSuccessHandler customizeAuthenticationSuccessHandler;
#Autowired
RestAuthenticationFailureHandler restAuthenticationFailureHandler;
#Autowired
UserDetailsService userDetailsService;
#Autowired
PasswordEncoder passwordEncoder;
#Autowired
private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
#Autowired
private AccessDeniedHandler restAccessDeniedHandler;
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(authenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests().antMatchers("/api/common/**").permitAll()
.antMatchers("/api/student/**").access("hasRole('ROLE_STUDENT')")
.antMatchers("/api/staff/**").access("hasRole('ROLE_STAFF')").antMatchers("/sysAdmin/**").access("hasRole('ROLE_ADMIN')").and().formLogin()
.loginProcessingUrl("/api/common/login")
.successHandler(customizeAuthenticationSuccessHandler)
.failureHandler(restAuthenticationFailureHandler)
.usernameParameter("userName")
.passwordParameter("password")
.and().exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint).accessDeniedHandler(restAccessDeniedHandler)
.and().csrf().disable();
}
#Bean
public UsernamePasswordAuthenticationFilter authenticationFilter() throws Exception {
AuthenticationFilter authFilter = new AuthenticationFilter();
authFilter.setUsernameParameter("username");
authFilter.setPasswordParameter("password");
authFilter.setAuthenticationManager(authenticationManagerBean());
return authFilter;
}
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider());
}
public AuthenticationProvider authProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder);
return provider;
}
}
Problem:
When I tried to authenticate user attemptAuthentication method of my custom filter class is not getting invoked and call directly go to UserDetails service class
Added FilterProcessingUrl to authenticateFilter object resolved the issue.
Updated authenticationFilter() method :
#Bean
public AuthenticationFilter authenticationFilter() throws Exception {
AuthenticationFilter authFilter = new AuthenticationFilter();
authFilter.setUsernameParameter("userName");
authFilter.setPasswordParameter("password");
authFilter.setFilterProcessesUrl("/api/common/login");
authFilter.setAuthenticationSuccessHandler(customizeAuthenticationSuccessHandler);
authFilter.setAuthenticationFailureHandler(restAuthenticationFailureHandler);
authFilter.setAuthenticationManager(authenticationManagerBean());
return authFilter;
}
I have a Spring Cloud (Edgeware.SR3) OAuth2 Authorization server configured with Custom JWT tokens. I'm getting an IllegalStateException, UserDetailsService is required error when I hit the token_refresh endpoint.
Does anyone have an example of doing a UserDetails Service for Active Directory for this scenario? I presume the call for refreshing the token is actually checking against AD if the user is still valid such as not disabled since last successful login.
Not shown is I'm also doing integrations to AWS Cognito in the custom token enhancer which is also all working. Just the refresh token is what remains.
#Configuration
public class ServiceConfig extends GlobalAuthenticationConfigurerAdapter {
#Value("${ldap.domain}")
private String DOMAIN;
#Value("${ldap.url}")
private String URL;
#Override
public void init(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider());
}
#Bean
public AuthenticationManager authenticationManager() {
return new ProviderManager(Arrays.asList(activeDirectoryLdapAuthenticationProvider()));
}
#Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(DOMAIN, URL);
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}
}
--------
#Configuration
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
private final AuthenticationManager authenticationManager;
#Autowired
public AuthorizationServerConfiguration(AuthenticationManager authenticationManager){
super();
this.authenticationManager = authenticationManager;
}
#Value("${signing.key}")
private String signingKey;
#Bean
public JwtAccessTokenConverter accessTokenConverter(){
final JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
accessTokenConverter.setSigningKey(signingKey);
return accessTokenConverter;
}
#Bean
public TokenStore tokenStore(){
return new JwtTokenStore(accessTokenConverter());
}
#Bean
#Primary
public DefaultTokenServices tokenServices(){
final DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(tokenStore());
tokenServices.setSupportRefreshToken(true);
return tokenServices;
}
#Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("${client.id}")
.secret("${client.secret}")
.authorizedGrantTypes("password","refresh_token","authorization_code","client_credentials")
.refreshTokenValiditySeconds(3600 *24)
.scopes("xx","xx")
.autoApprove("xxxx")
.accessTokenValiditySeconds(3600);
}
#Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints){
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(
Arrays.asList(tokenEnhancer(),accessTokenConverter()));
endpoints
.tokenStore(tokenStore())
.tokenEnhancer(tokenEnhancerChain)
.authenticationManager(authenticationManager)
.allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST)
.accessTokenConverter(accessTokenConverter());
}
#Override//oauth/check_token?token={access_token}
public void configure(final AuthorizationServerSecurityConfigurer security)throws Exception {
security.checkTokenAccess("permitAll()");
super.configure(security);
}
#Bean
public TokenEnhancer tokenEnhancer(){
return new CustomTokenEnhancer();
}
}
Is it possible to have a custom ldap authentication provider along with custom ldap authorities populator?
I don't want to restart my application each time ldap server is unreachable for a short moment (So i need the custom provider, to create a new context and override authenticate method on each login).
On the other side, i need to create custom roles for each membership of ldap user (need to override the getGrantedAuthorities)
For implementing custom ldap authentication provider you need to create class that extends from AbstractLdapAuthenticator
public class BindPasswordAuthentificator extends AbstractLdapAuthenticator {
public BindPasswordAuthentificator(BaseLdapPathContextSource contextSource) {
super(contextSource);
}
#Override
public DirContextOperations authenticate(Authentication authentication) {
DirContextOperations user;
String username = authentication.getName();
String password = (String)authentication.getCredentials();
user = authenticateByLdap(username, password); // authenticate user here
if (user == null) {
throw new BadCredentialsException(
messages.getMessage("BindAuthenticator.badCredentials", "Bad credentials"));
}
return user;
}
}
And for implementing ldap authorities populator you need to create class that extends from LdapAuthoritiesPopulator
public class CustomLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator {
#Override
public Collection<? extends GrantedAuthority> getGrantedAuthorities(DirContextOperations userData, String username) {
Collection<GrantedAuthority> gauth = new HashSet<>();
//you need to place logic for populating user authorities here
return gauth;
}
}
After that you need to configure these two classes in your configuration
#Configuration
#PropertySource("classpath:application.properties")
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Value("${life.ldap.server}")
private String ldapServer;
#Autowired
public void globalUserDetails(final AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(ldapAuthenticationProvider());
}
#Bean
public LdapAuthenticationProvider ldapAuthenticationProvider() {
return new LdapAuthenticationProvider(authentificator(), authPopulator());
}
#Bean
public BindPasswordAuthentificator authentificator() {
return new BindPasswordAuthentificator(contextSource());
}
#Bean
public DefaultSpringSecurityContextSource contextSource() {
return new DefaultSpringSecurityContextSource(ldapServer);
}
#Bean
public CustomLdapAuthoritiesPopulator authPopulator() {
CustomLdapAuthoritiesPopulator result = new CustomLdapAuthoritiesPopulator();
return result;
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/login").permitAll()
.antMatchers("/oauth/token/revokeById/**").permitAll()
.antMatchers("/tokens/**").permitAll()
.anyRequest().authenticated()
.and().formLogin().permitAll()
.and().csrf().disable();
}
}