I have client defined to use password grant without client secret, but when I'm calling for token It's not working and I'm getting a warning BCryptPasswordEncoder : Empty encoded password. Spring Security allows using clients without a secret, in AuthorizationServerSecurityConfigurer I fount that PasswordEncoder should match if encoded password is empty, but it never gets to that point, BasicAuthenticationFilter because stops it with different password encoder.
#Configuration
#EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
private final AuthenticationManager authenticationManager;
private final CustomerLoginService userDetailsService;
private final OAuthClientDetailsService oAuthClientDetailsService;
#Autowired
public AuthServerConfig(#Qualifier("authenticationManagerBean") AuthenticationManager authenticationManager,
CustomerLoginService userDetailsService,
OAuthClientDetailsService oAuthClientDetailsService) {
this.authenticationManager = authenticationManager;
this.userDetailsService = userDetailsService;
this.oAuthClientDetailsService = oAuthClientDetailsService;
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
oauthServer
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(oAuthClientDetailsService);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.tokenStore(new InMemoryTokenStore())
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
}
Related
Im trying to implement Authorisation server with password and client_credentials grant
#Configuration
#EnableAuthorizationServer
public class OAuthConfiguration extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
public PasswordEncoder passwordEncoder;
#Autowired
private DataSource dataSource;
#Autowired
private TokenStore jwtTokenStore;
#Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
#Autowired
private TokenEnhancer jwtTokenEnhancer;
#Bean
public TokenEnhancer jwtTokenEnhancer(){
return new JWTokenEnhancer();
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
enhancerChain.setTokenEnhancers(Arrays.asList(jwtTokenEnhancer(), jwtAccessTokenConverter));
endpoints
.authenticationManager(authenticationManager)
.tokenStore(jwtTokenStore)
.accessTokenConverter(jwtAccessTokenConverter)
.tokenEnhancer(enhancerChain);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
JdbcClientDetailsServiceBuilder jcsb = clients.jdbc(dataSource);
jcsb.passwordEncoder(passwordEncoder);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
oauthServer.passwordEncoder(passwordEncoder)
.tokenKeyAccess("permitAll()")
.checkTokenAccess("permitAll()");
}
}
web config file
Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Resource(name = "userService")
private UserDetailsService userDetailsService;
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public BCryptPasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
#Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(encoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.anonymous().disable()
.authorizeRequests()
.antMatchers("/api-docs/**").permitAll();
}
#Override
public void configure(WebSecurity web) throws Exception {
// Allow eureka client to be accessed without authentication
web.ignoring().antMatchers("/*/")//
.antMatchers("/eureka/**")//
.antMatchers(HttpMethod.OPTIONS, "/**"); // Request type options should be allowed.
}
}
#Configuration
public class JwtTokenConfig {
#Bean
public TokenStore jwtTokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
#Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
accessTokenConverter.setSigningKey("dev");
return accessTokenConverter;
}
}
i have configured client details to pick up from database -
When i try to get access token based on password grant im able to get the access token
but when i try to get access token based on grnat_type client credentials - im getting internal server error .
Please help to check on what is wrong with my implementation.
enter image description here
In your class OAuthConfiguration, check client configuration present in configure(ClientDetailsServiceConfigurer clients) method. It appears that the JDBC client details service is not able to find any client details.
JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
jdbcClientDetailsService.listClientDetails(); // This probably would be empty.
If so, configure JDBC client details service something like this:
clients.jdbc(dataSource).withClient(CLIEN_ID)
.secret(encoder.encode(CLIENT_SECRET))
.authorizedGrantTypes("password", "refresh_token", "client_credentials")
.scopes("read", "write")
.resourceIds(RESOURCE_ID).and().build();
Found the Issue .
public class JWTokenEnhancer implements TokenEnhancer{
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
Map<String, Object> info = new HashMap<>();
info.put("user-info", "user additional information...");
// User user = (User) authentication.getPrincipal();
// info.put("isAdmin", user.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()).contains("ROLE_ADMIN"));
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);
return accessToken;
}
The commented line was causing the issue as there was no user in case of client_credentials
I am trying to implement OAuth2 - SpringBoot authentication.
I have configured a path with permitAll(), but even though it is configured, it shows error
{
"error": "unauthorized",
"error_description": "Full authentication is required to access this resource"
}
I am using postman to test and simply trying to fetch all users in DB. When I call, the control is not coming to RestController. I would like to just get the users list and permitAll() is provided.
Can anyone please help ?
I am posting the code below.
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
DataSource dataSource;
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().
antMatchers(HttpMethod.POST, "/api/**").permitAll().
antMatchers(HttpMethod.POST,"/admin/**").hasAnyRole("ADMIN").
anyRequest().authenticated();
}
#Override
public void configure(AuthenticationManagerBuilder builder) throws Exception{
builder.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery("select usrnam,usrpwd, case when usrsta='A' then true else false end from chsusrmst where usrnam=?")
.authoritiesByUsernameQuery("select usrnam,usrtyp from chsusrmst where usrnam=?");
}
}
#RestController
#RequestMapping("/api")
public class UserController {
#Autowired
private BCryptPasswordEncoder passwordEncoder;
#Autowired
private UserRepository userRepository;
#PostMapping("/user/register")
public String register(#RequestBody User user) {
String encodedPassword = passwordEncoder.encode(user.getUserPassword());
user.setUserPassword(encodedPassword);
userRepository.save(user);
return "User created";
}
#PostMapping("/admin/findUser")
public User findUser(#RequestBody User user) {
return userRepository.findByUserName(user.getUserName());
}
#PostMapping("/user/findAllUsers")
public List<User> findAllUsers() {
return userRepository.findAll();
}
}
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
private final AuthenticationManager authenticationManager;
private final PasswordEncoder passwordEncoder;
private final UserDetailsService userDetailsService;
#Value("${jwt.clientId:client}")
private String clientId;
#Value("${jwt.client-secret:secret}")
private String clientSecret;
#Value("${jwt.signing-key:123}")
private String jwtSigningKey;
#Value("${jwt.accessTokenValidititySeconds:43200}") // 12 hours
private int accessTokenValiditySeconds;
#Value("${jwt.authorizedGrantTypes:password,authorization_code,refresh_token}")
private String[] authorizedGrantTypes;
#Value("${jwt.refreshTokenValiditySeconds:2592000}") // 30 days
private int refreshTokenValiditySeconds;
public AuthorizationServerConfig(AuthenticationManager authenticationManager, PasswordEncoder passwordEncoder, UserDetailsService userDetailsService) {
this.authenticationManager = authenticationManager;
this.passwordEncoder = passwordEncoder;
this.userDetailsService = userDetailsService;
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient(clientId)
.secret(passwordEncoder.encode(clientSecret))
.accessTokenValiditySeconds(accessTokenValiditySeconds)
.refreshTokenValiditySeconds(refreshTokenValiditySeconds)
.authorizedGrantTypes(authorizedGrantTypes)
.scopes("read", "write")
.resourceIds("api");
}
#Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.accessTokenConverter(accessTokenConverter())
.userDetailsService(userDetailsService)
.authenticationManager(authenticationManager);
}
#Bean
JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
return converter;
}
}
#Configuration
#EnableResourceServer
public class ResourceServer extends ResourceServerConfigurerAdapter {
#Override
public void configure(ResourceServerSecurityConfigurer serverSecurityConfigurer) {
serverSecurityConfigurer.resourceId("api");
}
}
Thanks for your consideration. I found the issue. HttpSecurity configuration was missing in Resource server and it has been resolved by adding below section.
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.antMatcher("/**")
.authorizeRequests()
.antMatchers("/user**").permitAll()
.antMatchers("/user/**").permitAll()
.antMatchers("/admin**").hasAuthority("ADMIN")
.antMatchers("/api/**").authenticated()
.anyRequest().authenticated();
I am very new to spring and it is my first attempt at spring security with oauth2. I have implemented OAuth2 with spring security and I do get the access token and the refresh token. However, while sending the refresh token to get the new access token I got "o.s.s.o.provider.endpoint.TokenEndpoint - IllegalStateException, UserDetailsService is required."
The solution to similar problem by other users appeared to be attaching UserDetailsService with the endpoint.
So I did the same and now when I try to send the request to with grant_type: refresh_token and refresh_token: THE TOKEN along with the client id and secret, I get an error that the user was not found.
Please refer the WebSecurityConfiguration class below:
#EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter{
#Autowired
private UserDetailsService customUserDetailsService;
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean ();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService)
.passwordEncoder(encoder());
}
#Override
protected void configure (HttpSecurity http) throws Exception {
http.csrf().disable()
.antMatcher("/**")
.authorizeRequests()
.antMatchers("/login**")
.permitAll()
.anyRequest()
.authenticated();
}
public PasswordEncoder encoder() {
return NoOpPasswordEncoder.getInstance();
}
}
Please refer the AuthorizationServerConfiguration class below:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private DataSource dataSource;
#Autowired
private CustomUserDetailsService userDetailsService;
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.tokenStore(tokenStore());
.userDetailsService(userDetailsService);
}
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
}
Please refer the ResourceServerConfiguration class below:
#Configuration
#EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter{
#Autowired
DataSource dataSource;
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("scout").tokenStore(tokenStore());
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests (). antMatchers ("/oauth/token", "/oauth/authorize **").permitAll();
// .anyRequest (). authenticated ();
http.requestMatchers (). antMatchers ("/api/patients/**") // Deny access to "/ private"
.and (). authorizeRequests ()
.antMatchers ("/api/patients/**"). access ("hasRole ('PATIENT')")
.and (). requestMatchers (). antMatchers ("/api/doctors/**") // Deny access to "/ admin"
.and (). authorizeRequests ()
.antMatchers ("/api/doctors/**"). access ("hasRole ('DOCTOR')");
}
}
The CustomUserDetailsService class for reference if required:
#Service
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private UsersRepository userRepository;
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
Optional<Users> usersOptional = userRepository.findByEmail(email);
Users user = null;
if(usersOptional.isPresent()) {
System.out.println(usersOptional.isPresent());
user = usersOptional.get();
}else {
throw new RuntimeException("Email is not registered!");
}
return new CustomUserDetails(user);
}
}
As I think, the server should only check for the validity of the refresh token as we don't pass the user details with refresh token. So I don't know why it requires the userDetails in the first place.
Please help and guide if I am missing something!
Thanks in advance.
I don't sure. But as I see your code in WebSecurityConfiguration could wired default InMemoryUserDetailsManager UserDetailsService .That could be reason why you have 2 different provider. In one you write, from the other you read users. Please try change your code as I show below and let me know if it help:
Was:
#Autowired
private UserDetailsService customUserDetailsService;
My vision how should be:
#Autowired
private CustomUserDetailsService customUserDetailsService;
I am trying to develop a rest api with spring security Using OAuth2 implementation. but how do I remove basic authentication. I just want to send a username and password to body and get a token on postman.
#Configuration
public class OAuthServerConfigration {
private static final String SERVER_RESOURCE_ID = "oauth2-server";
private static InMemoryTokenStore tokenStore = new InMemoryTokenStore();
#Configuration
#EnableResourceServer
protected static class ResourceServer extends ResourceServerConfigurerAdapter {
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(SERVER_RESOURCE_ID).stateless(false);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.anonymous().disable().requestMatchers().antMatchers("/api/**").and().authorizeRequests().antMatchers("/api/**").access("#oauth2.hasScope('read')");
}
}
#Configuration
#EnableAuthorizationServer
protected static class AuthConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager).tokenStore(tokenStore).approvalStoreDisabled();
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("client")
.secret("$2a$10$5OkeCLKNs/BkdO0qcYRri.MdIcKhFvElAllhPgLfRQqG7wkEiPmq2")
.authorizedGrantTypes("password","authorization_code","refresh_token")
.authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
.scopes("read", "write", "trust")
.resourceIds(SERVER_RESOURCE_ID)
//.accessTokenValiditySeconds(ONE_DAY)
.accessTokenValiditySeconds(300)
.refreshTokenValiditySeconds(50);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer
// we're allowing access to the token only for clients with 'ROLE_TRUSTED_CLIENT' authority
.tokenKeyAccess("hasAuthority('ROLE_TRUSTED_CLIENT')")
.checkTokenAccess("hasAuthority('ROLE_TRUSTED_CLIENT')");
}
}
}
#Configuration
#Order(2)
public static class ApiLoginConfig extends
WebSecurityConfigurerAdapter{
#Autowired
DataSource dataSource;
#Autowired
ClientDetailsService clientDetailsService;
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/oauth/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().disable().csrf().disable().antMatcher("/oauth/token").authorizeRequests().anyRequest().permitAll();
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
#Bean
#Autowired
public TokenStoreUserApprovalHandler userApprovalHandler(TokenStore tokenStore){
TokenStoreUserApprovalHandler handler = new TokenStoreUserApprovalHandler();
handler.setTokenStore(tokenStore);
handler.setRequestFactory(new DefaultOAuth2RequestFactory(clientDetailsService));
handler.setClientDetailsService(clientDetailsService);
return handler;
}
#Bean
#Autowired
public ApprovalStore approvalStore(TokenStore tokenStore) throws Exception {
TokenApprovalStore store = new TokenApprovalStore();
store.setTokenStore(tokenStore);
return store;
}
}
want to remove the basic authentication and send the username password in the body tag from the postman for get token
and I have got some problem
{
"error": "unauthorized",
"error_description": "There is no client authentication. Try adding an appropriate authentication filter."
}
In your #EnableAuthorizationServer configuration class in the method:-
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer)
Try to add the following:-
oauthServer.allowFormAuthenticationForClients()
After you have done that you will have to call the oauth get token url as below:-
URL will be the same as http(s)://{HOST_NAME}/oauth/token
HTTP method type now will be POST
Header:-
Content-Type=application/x-www-form-urlencoded
Parameters will be key value pairs in x-www-form-urlencoded in the body of postman
for client_credentials grant_type:-
grant_type=client_credentials
client_id=client_id_value
client_secret=client_secret_value
scope=scopes
for password grant_type:-
grant_type=password
client_id=client_id_value
client_secret=client_secret_value
scope=scopes
username=username
password=password
scopes will be comma separated here
I have an API that I've developed in Spring Boot, and I've just noticed that it's not returning a refresh token when you request an access token.
The response from the API looks like this;
{
"access_token": "ed0bdc62-dccf-4f58-933c-e28ad9598843",
"token_type": "bearer",
"expires_in": 2589494,
"scope": "read write"
}
My configuration looks like this;
#Configuration
public class OAuth2ServerConfiguration {
private static final String RESOURCE_ID = "myapi";
#Autowired
DataSource dataSource;
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Autowired
TokenStore tokenStore;
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources
.resourceId(RESOURCE_ID)
.tokenStore(tokenStore);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/oauth/**", "/view/**").permitAll()
.anyRequest().authenticated();
}
}
#Configuration
#EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
#Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
#Autowired
private DataSource dataSource;
#Autowired
private TokenStore tokenStore;
#Autowired
private CustomUserDetailsService userDetailsService;
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenStore(tokenStore)
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.jdbc(dataSource);
}
}
}
I previously had the project setup to use JWT for access tokens and that did return a refresh token, however I had to remove JWT as it wasn't compatible with using the token store.
To confirm, it returns a refresh token when the grant_type = password, but not when it's set to 'client_credentials'.
Does anyone have any suggestions why my configuration doesn't return a refresh token?
4.3.3. Access Token Response in RFC 6749 (The OAuth 2.0 Authorization Framework) says "A refresh token SHOULD NOT be included." Therefore, most implementations of OAuth 2.0 authorization servers do not generate a refresh token in Client Credentials flow.
I got the same issue, then I changed in this method, I added REFRESH_TOKEN then in the response I am getting refresh_token value.
static final String REFRESH_TOKEN = "refresh_token";
#Override
public void configure(ClientDetailsServiceConfigurer configurer) throws Exception {
configurer
.inMemory()
.withClient(CLIENT_ID)
.secret(messageDigestPasswordEncoder.encode(CLIENT_SECRET))
.authorizedGrantTypes(GRANT_TYPE,REFRESH_TOKEN)
.scopes(SCOPE_READ, SCOPE_WRITE ,TRUST)
.accessTokenValiditySeconds(ACCESS_TOKEN_VALIDITY_SECONDS).
refreshTokenValiditySeconds(REFRESH_TOKEN_VALIDITY_SECONDS);
}