I am new to Spring Boot, and I am trying to configure OAuth 2.0. The problem I am having at this moment is that I keep getting the following message when I attempt to request for an access token:
{
"error": "invalid_grant",
"error_description": "Bad credentials"
}
The error message in the Spring Boot console says that the user cannot be found.
: Authentication attempt using org.springframework.security.authentication.dao.DaoAuthenticationProvider
: User 'stromero' not found
: Returning cached instance of singleton bean 'authenticationAuditListener'
I have implemented a custom user that has already been saved to a database using JPA, I am unable to figure why Spring Security cannot find this user, it may an issue with my logic or configuration. If someone with more experience can look at my code and perhaps guide me to the right direction, that would be greatly appreciated.
This is the HTTP Request:
POST /oauth/token HTTP/1.1
Host: localhost:8181
Authorization: Basic YnJvd3NlcjpzZWNyZXQ=
Cache-Control: no-cache
Content-Type: application/x-www-form-urlencoded
username=stromero&password=password&client_id=browser&client_secret=secret&grant_type=password
These are the classes that I used to implement my custom user and OAuth 2.0
#Repository
public interface UserRepository extends CrudRepository<CustomUser, String> {
public CustomUser findByUsername(String name);
}
Below is the custom user I have created
#Entity
#Table (name = "custom_user")
public class CustomUser {
#Id
#Column(name = "id", nullable = false, updatable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "username", unique=true, nullable = false)
private String username;
#Column(name = "password", nullable = false)
private String password;
#ElementCollection
private List<String> roles = new ArrayList<>();
public List<String> getRoles() {
return roles;
}
public void setRoles(List<String> roles) {
this.roles = roles;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
Below is a customdetails service that reads the user information from the database and returns it as a UserDetails Object
#Service
#Transactional(readOnly = true)
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
CustomUser customUser = userRepository.findByUsername(s);
boolean enabled = true;
boolean accountNonExpired = true;
boolean credentialsNonExpired = true;
boolean accountNonLocked = true;
return new User(
customUser .getUsername(),
customUser .getPassword().toLowerCase(),
enabled,
accountNonExpired,
credentialsNonExpired,
accountNonLocked,
getAuthorities(customUser.getRoles()));
}
public Collection<? extends GrantedAuthority> getAuthorities(List<String> roles) {
List<GrantedAuthority> authList = getGrantedAuthorities(roles);
return authList;
}
public static List<GrantedAuthority> getGrantedAuthorities(List<String> roles) {
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
for (String role : roles) {
authorities.add(new SimpleGrantedAuthority(role));
}
return authorities;
}
}
The class below is a data structure that holds both the UserDetailsService and ClientDetailsService
public class ClientAndUserDetailsService implements UserDetailsService,
ClientDetailsService {
private final ClientDetailsService clients;
private final UserDetailsService users;
private final ClientDetailsUserDetailsService clientDetailsWrapper;
public ClientAndUserDetailsService(ClientDetailsService clients,
UserDetailsService users) {
super();
this.clients = clients;
this.users = users;
clientDetailsWrapper = new ClientDetailsUserDetailsService(this.clients);
}
#Override
public ClientDetails loadClientByClientId(String clientId)
throws ClientRegistrationException {
return clients.loadClientByClientId(clientId);
}
#Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
UserDetails user = null;
try{
user = users.loadUserByUsername(username);
}catch(UsernameNotFoundException e){
user = clientDetailsWrapper.loadUserByUsername(username);
}
return user;
}
}
The class below is my configuration for OAuth 2.0 using Spring Boot
#Configuration
public class OAuth2SecurityConfiguration {
#Configuration
#EnableWebSecurity
protected static class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
protected void registerAuthentication(
final AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
}
#Configuration
#EnableResourceServer
protected static class ResourceServer extends
ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests().antMatchers("/oauth/token").anonymous();
// Require all GET requests to have client "read" scope
http.authorizeRequests().antMatchers(HttpMethod.GET, "/**")
.access("#oauth2.hasScope('read')");
// Require all POST requests to have client "write" scope
http.authorizeRequests().antMatchers(HttpMethod.POST,"/**")
.access("#oauth2.hasScope('write')");
}
}
#Configuration
#EnableAuthorizationServer
#Order(Ordered.LOWEST_PRECEDENCE - 100)
protected static class AuthorizationServer extends
AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
private ClientAndUserDetailsService combinedService;
public AuthorizationServer() throws Exception {
ClientDetailsService clientDetailsService = new InMemoryClientDetailsServiceBuilder()
.withClient("browser")
.secret("secret")
.authorizedGrantTypes("password")
.authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
.scopes("read","write")
.resourceIds("message")
.accessTokenValiditySeconds(7200)
.and()
.build();
// Create a series of hard-coded users.
UserDetailsService userDetailsService = new CustomUserDetailsService();
combinedService = new ClientAndUserDetailsService(clientDetailsService, userDetailsService);
}
#Bean
public ClientDetailsService clientDetailsService() throws Exception {
return combinedService;
}
#Bean
public UserDetailsService userDetailsService() {
return combinedService;
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints.authenticationManager(authenticationManager);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients)
throws Exception {
clients.withClientDetails(clientDetailsService());
}
}
}
Below is my pom.xml file
<properties>
<tomcat.version>8.0.8</tomcat.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- Postgres JDBC Driver -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.2-1002-jdbc4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- Hibernate validator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.0.3.RELEASE</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>17.0</version>
</dependency>
</dependencies>
Yeah, I had the same issue... wanted to use JPA's UserDetailsService but the same problem - user couldn't be found... got resolved it eventually, thanks to Dave Syer's OAuth2 samples on GitHub.
The problem seem to be in authenticationManager instance autowired in #EnableAuthorizationServer AuthorizationServer class. AuthenticationManager is autowired there and seems to initialize with default DAOAuthenticationProvider, and for some reason it doesn't use custom JPA UserDetailsService we initialize authenticationManager with in WebSecurityConfiguration.
In Dave Syer samples authenticationManager is exposed as a bean in WebSecurityConfiguration:
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
then in AuthorizationServer we autowire authenticationManager as follows:
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
Once I did it I finally managed to get my user authenticated against my customer JPA user repository.
I faced the same issue and spent hours investigating the case. As a workaround, if you are using Spring Boot version 1.1.8.RELEASE, downgrade it to 1.0.2.RELEASE. Things went fine that way but I did not investigate yet the reasons of compatibility issue with Spring Boot version 1.1.8.RELEASE.
InitializeUserDetailsBeanManagerConfigurer has default order as
static final int DEFAULT_ORDER = Ordered.LOWEST_PRECEDENCE - 5000;
So it Initializee DaoAuthenticationProvider before custom one.
#Order(-5001)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { ... }
Related
I have micro-services running using old Spring Authorization Server and Resource Server (org.springframework.cloud:spring-cloud-starter-oauth2 deprecated now).
For new micro-service I am creating, I need to use newer version of Spring Security 5 implementation of Resource Server. (org.springframework.boot:spring-boot-starter-oauth2-resource-server)
Below is code from my authorization server.
#Configuration
public class AuthorizationServerConfig implements AuthorizationServerConfigurer {
private final PasswordEncoder passwordEncoder;
private final DataSource dataSource;
private final AuthenticationManager authenticationManager;
private final UserDetailsService userDetailsService;
private final CustomAccessTokenConverter customAccessTokenConverter;
#Value("${signing-key:123}")
private String signingKey;
public AuthorizationServerConfig(
PasswordEncoder passwordEncoder,
DataSource dataSource,
AuthenticationManager authenticationManager,
UserDetailsService userDetailsService,
CustomAccessTokenConverter customAccessTokenConverter) {
this.passwordEncoder = passwordEncoder;
this.dataSource = dataSource;
this.authenticationManager = authenticationManager;
this.userDetailsService = userDetailsService;
this.customAccessTokenConverter = customAccessTokenConverter;
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
final JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setAccessTokenConverter(customAccessTokenConverter);
jwtAccessTokenConverter.setSigningKey(signingKey);
return jwtAccessTokenConverter;
}
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Bean
public ApprovalStore approvalStore() {
return new JdbcApprovalStore(dataSource);
}
#Bean
public AuthorizationCodeServices authorizationCodeServices() {
return new JdbcAuthorizationCodeServices(dataSource);
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
final DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
return defaultTokenServices;
}
#Bean
public TokenEnhancer tokenEnhancer() {
return new CustomTokenEnhancer();
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.checkTokenAccess("isAuthenticated()").tokenKeyAccess("permitAll()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource).passwordEncoder(passwordEncoder);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), accessTokenConverter()));
endpoints.tokenStore(tokenStore());
endpoints.approvalStore(approvalStore());
endpoints.authorizationCodeServices(authorizationCodeServices());
endpoints.authenticationManager(authenticationManager);
endpoints.tokenEnhancer(tokenEnhancerChain);
endpoints.userDetailsService(userDetailsService);
}
}
How do I use the symmetric signing key to decrypt JWT in Spring Security 5?
From migration guide, https://github.com/spring-projects/spring-security/wiki/OAuth-2.0-Migration-Guide I have option to use JWT+JWK or JWT+Key but not sure what to use as my signing key is just a string.
I had faced exactly the same problem mentioned above. I have the old oaut2 Authorization Service and migrate the old Resource Service to Spring Security 5 standards.
So, it's my proposition of this problem. I chose JWT + Key scenario from OAuth2 Migration Guide.
In application.yml file I added the same singleKey and additional configuration for debugging SecurityFilterChain to be able to control authorization flow according to docs:
spring:
security:
oauth2:
resourceserver:
jwt:
key-value: zSigFffuyF5CjmLAxubSTVBt5OnfbnGPkFpvNpg2n/.....
logging:
level:
org:
springframework:
security: DEBUG
In my configuration class, I injected singleKey by #Value annotation:
#Value("${spring.security.oauth2.resourceserver.jwt.key-value}")
private String key;
then I exposed default configuration on SecurityFIlterChain:
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated())
.oauth2ResourceServer((oauth2) -> oauth2.jwt((jwt) -> jwt.decoder(jwtDecoder())));
return http.build();
}
and for customized JTWDecoder bean adding converting of my singleKey to SecretKey object:
#Bean
public JwtDecoder jwtDecoder() {
byte[] secretByte = key.getBytes();
SecretKey secretKey = new SecretKeySpec(secretByte, 0, secretByte.length, "RSA");
return NimbusJwtDecoder.withSecretKey(secretKey).build();
}
Logs withs configured debbugig:
o.s.security.web.FilterChainProxy.doFilterInternal - Securing GET /example_endpoint
o.s.s.w.c.SecurityContextPersistenceFilter.doFilter - Set SecurityContextHolder to empty SecurityContext
o.s.s.o.s.r.a.JwtAuthenticationProvider.authenticate - Authenticated token
o.s.s.o.s.r.w.BearerTokenAuthenticationFilter.doFilterInternal - Set SecurityContextHolder to JwtAuthenticationToken [Principal=org.springframework.security.oauth2.jwt.Jwt#29cc3dff, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=172.16.57.7, SessionId=null], Granted Authorities=[SCOPE_BOU]]
o.s.security.web.FilterChainProxy.doFilter - Secured GET /example_endpoint
Like in docs: https://docs.spring.io/spring-security/reference/5.7.2/servlet/oauth2/resource-server/jwt.html#oauth2resourceserver-jwt-architecture
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 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();
}
}
I am trying to add a custom login page for my boot strap application. I was following this tutorial. I couldn't make work with my custom login page.
Here is my pom.xml:
...
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<scope>test</scope>
</dependency>
...
MvcConfig.java
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
#Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("forward:/index.html");
registry.addViewController("/login").setViewName("login");
}
}
FrontendApp.java:
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;
import ch.qos.logback.classic.Logger;
#SpringBootApplication
#Import(value = MvcConfig.class)
public class FrontendApp {
private static Logger logger = (Logger) LoggerFactory.getLogger(FrontendApp.class);
public static void main(String[] args) {
SpringApplication app = new SpringApplication(FrontendApp.class);
app.run(args);
}
}
SecurityConfiguration.java
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthenticationProvider customAuthenticationProvider;
#Autowired
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(this.customAuthenticationProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/css/**").permitAll()
.antMatchers("/resources/**").permitAll()
.antMatchers("**").permitAll()
.antMatchers("/login").permitAll()
.anyRequest().authenticated().and()
.formLogin()
.loginPage("/login");
}
}
I opened all the url's so Ican just check whether I can see /login or not.
CustomAuthenticationProvider.java
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
private static final Logger logger = LoggerFactory.getLogger(CustomAuthenticationProvider.class);
public CustomAuthenticationProvider() {
logger.info("*** CustomAuthenticationProvider created");
}
#Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if(authentication.getName().equals("karan") && authentication.getCredentials().equals("saman")) {
List<GrantedAuthority> grantedAuths = new ArrayList<>();
grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER"));
grantedAuths.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
return new UsernamePasswordAuthenticationToken(authentication.getName(), authentication.getCredentials(), grantedAuths);
} else {
return null;
}
}
}
When I try localhost:8080/login I will get the following error:
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
There was an unexpected error (type=Internal Server Error, status=500).
Error resolving template "login", template might not exist or might not be accessible by any of the configured Template Resolvers
However when I try localhost:8080/ it will successfully redirect to index.html as I specified in MvcConfig.java.
Here is my login.html code:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
<meta charset="utf-8" />
<title>k</title>
</head>
I paste my login.html in /src/main/resources/templates and /src/main/webapp/ and /src/main/webapp/templates it still didn't work!
OK, so it was a simple mistake in pom.xml.
<!--<resources>-->
<!--<resource>-->
<!--<directory>src/main/resources</directory>-->
<!--<includes>-->
<!--<include>*</include>-->
<!--</includes>-->
<!--<filtering>true</filtering>-->
<!--</resource>-->
<!--</resources>-->
After I commented out these (as you see) from the pom file it worked perfectly.At least the above codes might be useful to someone else.
I'm playing around with spring-security-oauth2. I try to build some microservices with an authentication backend.
I set up an simple spring boot project with the following dependencies
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
and one Configuration Class
#Configuration
public class SecurityConfiguration {
#Autowired
#Qualifier("clientDetailsServiceBean")
private ClientDetailsService clientDetailsService;
#Autowired
#Qualifier("userDetailsServiceBean")
private UserDetailsService userDetailsService;
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(jsr250Enabled = true, securedEnabled = true, prePostEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
#Bean(name = "authenticationManagerBean")
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().permitAll().and().userDetailsService(userDetailsService).formLogin().and().httpBasic();
}
}
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager).tokenStore(tokenStore());
}
#Bean
public ApprovalStore approvalStore() throws Exception {
TokenApprovalStore store = new TokenApprovalStore();
store.setTokenStore(tokenStore());
return store;
}
#Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetailsService);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.checkTokenAccess("permitAll()");
security.allowFormAuthenticationForClients();
}
}
My Implementation of Client- and UserDetailsService are very simple and always returns an object
#Service("clientDetailsServiceBean")
public class ClientDetailsServiceBean implements ClientDetailsService {
private static final Logger LOGGER = LoggerFactory.getLogger(ClientDetailsServiceBean.class);
#Override
public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {
LOGGER.info("Load client {}", clientId);
BaseClientDetails details = new BaseClientDetails();
details.setClientId(clientId);
details.setAuthorizedGrantTypes(Arrays.asList("password", "refresh_token", "client_credentials"));
details.setScope(Arrays.asList("trust"));
details.setAutoApproveScopes(Arrays.asList("trust"));
details.setAuthorities(Arrays.asList(new SimpleGrantedAuthority("client_role2")));
details.setResourceIds(Arrays.asList("clients"));
details.setClientSecret("secret");
return details;
}
}
#Service("userDetailsServiceBean")
public class UserDetailsServiceBean implements UserDetailsService {
private static final Logger LOGGER = LoggerFactory.getLogger(UserDetailsServiceBean.class);
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
LOGGER.info("Load user {}", username);
return new User(username, "password", Arrays.asList(new SimpleGrantedAuthority("ROLE_USER")) );
}
}
But, when i try to receive an accessToken via
curl http://localhost:8081/oauth/token -d grant_type=client_credentials -d client_id=web_client -d client_secret=secret
i receive an error "Full authentication is required to access this resource" and when i try
curl http://localhost:8081/oauth/token -d grant_type=client_credentials -d client_id=web_client -d client_secret=secret --user web_client:secret
i receive an error "Bad credentials". From my point of view both should work, but it seems like my configuration is missing.
There are other things with OAuth that unclear to me:
I try to build an spring-mvc application with spring-security and a custom login form. It's possible to handle token request and refresh cycles by spring security without redirect to the authentication app?
In case of event driven application, it's possible to ensure the token is valid? In case of failure, the user clicks on button and an event is written but the processing of this will be hours later. How can i process the event with the user credentials?
Your inner #Configuration classes need to be static. I'm surprised the app starts at all, and probably the whole of your SecurityConfiguration is actually not being used.
It's possible to handle token request and refresh cycles by spring security without redirect to the authentication app?
Naturally. Did you read about the password and refresh_token grants in the spec? But in a web UI you are strongly advised to use the auth code grant (with the redirects), so that the user only enters his credentials in a trusted place.
the user clicks on button and an event is written but the processing of this will be hours later. How can i process the event with the user credentials?
Refresh tokens might be the best approach. The event obviously needs to be secure since it will have to contain the refresh token.