I'm trying to make a project with APIs with cognito authentication. For this, I need a CustomAuthenticationProvider to get user's roles from another microservice. But it is never invoked.
application.yml
spring:
security:
oauth2:
client:
registration:
cognito:
clientId: xxxx
clientSecret: xxxxx
scope: openid
redirectUriTemplate: "http://localhost:8080/login/oauth2/code/cognito"
clientName: xxxx
provider:
cognito:
issuerUri: https://cognito-idp.us-west-2.amazonaws.com/us-west-2_xxxx
usernameAttribute: cognito:username
build.gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security'
compile 'org.springframework.security:spring-security-oauth2-jose:5.3.3.RELEASE'
compile 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework:spring-tx:5.2.7.RELEASE'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
securityConfig
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity( securedEnabled = true )
#ComponentScan( "com.project" )
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthenticationProvider authProvider;
#Override
protected void configure ( final AuthenticationManagerBuilder auth ) throws Exception {
auth.authenticationProvider( this.authProvider );
}
#Override
protected void configure ( final HttpSecurity http ) throws Exception {
http.cors();
http.csrf().disable();
http.authorizeRequests()
.antMatchers( "/public/**" )
.permitAll()
.antMatchers( "/user/**" )
.hasRole( "USER" )
.anyRequest()
.authenticated()
.and()
.oauth2Client()
.and()
.authenticationProvider( new CustomAuthenticationProvider() );
}
}
CustomAuthenticationProvider
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Override
public Authentication authenticate ( final Authentication authentication ) throws AuthenticationException {
String userName = authentication.getName();
System.out.println( userName );
return new AWSAuthentication( userName, Arrays.asList( new SimpleGrantedAuthority( "ROLE_USER" ) ) );
}
#Override
public boolean supports ( Class<?> authentication ) {
return authentication.equals( AWSAuthentication.class );
}
}
No error occur, only 403.
I saw this example, but it is deprecated.
Could anyone help me with some clue or example?
I solved my problem adapting the code of this lib to my necessity.
Related
I am trying to implement Single Sign On with Spring Security OAuth2 and JWT.
I use two separate applications:
An Authorization Server – which is the central authentication mechanism
Client Application: the applications using SSO
When a user tries to access a secured page in the client app, they’ll be redirected to authenticate first, via the Authentication Server.
And I am using the Authorization Code grant type out of OAuth2 to drive the delegation of authentication.
Authorization server:
#Configuration
#EnableAuthorizationServer
public class OAuth2AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
public static final Logger LOGGER = LoggerFactory.getLogger(AuthorizationServerConfigurerAdapter.class);
#Autowired
private AuthenticationManager authenticationManager;
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("abcd");
return converter;
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
defaultTokenServices.setTokenEnhancer(accessTokenConverter());
return defaultTokenServices;
}
#Override
public void configure(ClientDetailsServiceConfigurer clientDetailsServiceConfigurer) throws Exception {
clientDetailsServiceConfigurer
.inMemory()
.withClient("webapp")
.secret("Pass")
.authorizedGrantTypes("implicit", "refresh_token", "password", "authorization_code")
.scopes("user_info")
.autoApprove(true);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager);
}
}
Security Configuration on Authorization Server
#Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Value("${ldap.url}")
private String ldapUrl;
#Value("${ldap.userDnPatterns}")
private String ldapUserDnPatterns;
#Autowired
private PersonService personService;
#Autowired
private RoleService roleService;
#Override
protected void configure(HttpSecurity http) throws Exception { // #formatter:off
http.requestMatchers()
.antMatchers("/login", "/oauth/authorize")
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.permitAll();
} // #formatter:on
#Bean(name = "authenticationManager")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationProvider(this.ldapAndDatabaseAuthenticationProvider());
}
#Bean(name="ldapAuthenticationProvider")
public AuthenticationProvider ldapAndDatabaseAuthenticationProvider(){
LdapUserDetailsMapper userDetailsMapper = new LdapUserDetailsMapper();
userDetailsMapper.setRoleAttributes(new String[]{"groupMembership"});
LdapAndDatabaseAuthenticationProvider provider =
new LdapAndDatabaseAuthenticationProvider(
this.ldapAuthenticator(),
this.ldapAuthoritiesPopulator(),
this.personService);
provider.setUserDetailsContextMapper(userDetailsMapper);
return provider;
}
#Bean( name = "ldapAuthoritiesPopulator" )
public LdapAndDatabaseAuthoritiesPopulator ldapAuthoritiesPopulator(){
return new LdapAndDatabaseAuthoritiesPopulator(this.contextSource(), "", roleService);
}
#Bean( name = "ldapAuthenticator" )
public LdapAuthenticator ldapAuthenticator() {
BindAuthenticator authenticator = new BindAuthenticator( this.contextSource() );
authenticator.setUserDnPatterns(new String[]{"cn={0},ou=prod,o=TEMP"});
return authenticator;
}
#Bean( name = "contextSource" )
public DefaultSpringSecurityContextSource contextSource() {
DefaultSpringSecurityContextSource contextSource =
new DefaultSpringSecurityContextSource( ldapUrl );
return contextSource;
}
}
application.properties:
server.port=8888
server.context-path=/auth
security.basic.enabled=false
When I login the client application, It correctly forwards to Authorization Server for Single Sign On.
I enter the user credentials. User successfully get authenticated, but then I see the below error on browser:
OAuth Error
error="invalid_grant", error_description="A redirect_uri can only be
used by implicit or authorization_code grant types."
URL Shows:
http://localhost:8888/auth/oauth/authorize?client_id=webapp&redirect_uri=http://localhost:8080/jwt/webapp&response_type=code&state=LGvAzj
I also see the below at the log:
02:14:43.610 [http-nio-8888-exec-6] DEBUG o.s.s.o.p.e.FrameworkEndpointHandlerMapping/getHandlerInternal Looking up handler method for path /oauth/authorize
02:14:43.614 [http-nio-8888-exec-6] DEBUG o.s.s.o.p.e.FrameworkEndpointHandlerMapping/getHandlerInternal Returning handler method [public org.springframework.web.servlet.ModelAndView org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.authorize(java.util.Map<java.lang.String, java.lang.Object>,java.util.Map<java.lang.String, java.lang.Str ing>,org.springframework.web.bind.support.SessionStatus,java.security.Principal)]
02:14:43.849 [http-nio-8888-exec-6] INFO o.s.s.o.p.e.AuthorizationEndpoint/handleOAuth2Exception Handling OAuth2 error: error="invalid_grant", error_description="A redirect_uri can only be used by implicit or authorization_code grant types."
Can you please help me to find the problem?
UPDATE
Actually, Dur is right. This configuration is correct and works fine. I had another configuration file which configures JdbcClientDetails and it was overwriting the clientDetailsService created with inmemory in this configuration.
I’ve used Jhipster to generate an app with the security option OAuth 2.0 / OIDC Authentication. I reconfigured said app to use Okta instead of keycloak following the instructions at http://www.jhipster.tech/security/#okta. All works as expected and the login flow performs as expected.
I now want to use OAuth 2.0 access_tokens to access my api resources from additional clients (Postman, Wordpress). I’ve retrieved a valid token from Okta added it to my Postman get request for localhost:8080/api/events and get a 401 in response.
The logs (https://pastebin.com/raw/R3D0GHHX) show that the spring security oauth2 doesn’t seem to be triggered by the presence of the Authorization bearer token.
Does Jhipster with OAuth 2.0 / OIDC Authentication support
access_token in the Authorization bearer header or url param out of
the box?
If not can you suggest what additional configurations I should make?
OAuth2Configuration.java
#Configuration
#Profile("dev")
public class OAuth2Configuration {
public static final String SAVED_LOGIN_ORIGIN_URI = OAuth2Configuration.class.getName() + "_SAVED_ORIGIN";
private final Logger log = LoggerFactory.getLogger(OAuth2Configuration.class);
#Bean
public FilterRegistrationBean saveLoginOriginFilter() {
Filter filter = new OncePerRequestFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
if (request.getRemoteUser() == null && request.getRequestURI().endsWith("/login")) {
String referrer = request.getHeader("referer");
if (!StringUtils.isBlank(referrer) &&
request.getSession().getAttribute(SAVED_LOGIN_ORIGIN_URI) == null) {
log.debug("Saving login origin URI: {}", referrer);
request.getSession().setAttribute(SAVED_LOGIN_ORIGIN_URI, referrer);
}
}
filterChain.doFilter(request, response);
}
};
FilterRegistrationBean bean = new FilterRegistrationBean(filter);
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
#Bean
public static DefaultRolesPrefixPostProcessor defaultRolesPrefixPostProcessor() {
return new DefaultRolesPrefixPostProcessor();
}
public static class DefaultRolesPrefixPostProcessor implements BeanPostProcessor, PriorityOrdered {
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof FilterChainProxy) {
FilterChainProxy chains = (FilterChainProxy) bean;
for (SecurityFilterChain chain : chains.getFilterChains()) {
for (Filter filter : chain.getFilters()) {
if (filter instanceof OAuth2ClientAuthenticationProcessingFilter) {
OAuth2ClientAuthenticationProcessingFilter oAuth2ClientAuthenticationProcessingFilter =
(OAuth2ClientAuthenticationProcessingFilter) filter;
oAuth2ClientAuthenticationProcessingFilter
.setAuthenticationSuccessHandler(new OAuth2AuthenticationSuccessHandler());
}
}
}
}
return bean;
}
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
#Override
public int getOrder() {
return PriorityOrdered.HIGHEST_PRECEDENCE;
}
}
}
SecurityConfiguration.java
#Configuration
#Import(SecurityProblemSupport.class)
#EnableOAuth2Sso
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final CorsFilter corsFilter;
private final SecurityProblemSupport problemSupport;
public SecurityConfiguration(CorsFilter corsFilter, SecurityProblemSupport problemSupport) {
this.corsFilter = corsFilter;
this.problemSupport = problemSupport;
}
#Bean
public AjaxLogoutSuccessHandler ajaxLogoutSuccessHandler() {
return new AjaxLogoutSuccessHandler();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(HttpMethod.OPTIONS, "/**")
.antMatchers("/app/**/*.{js,html}")
.antMatchers("/i18n/**")
.antMatchers("/content/**")
.antMatchers("/swagger-ui/index.html")
.antMatchers("/test/**")
.antMatchers("/h2-console/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.addFilterBefore(corsFilter, CsrfFilter.class)
.exceptionHandling()
.authenticationEntryPoint(problemSupport)
.accessDeniedHandler(problemSupport)
.and()
.logout()
.logoutUrl("/api/logout")
.logoutSuccessHandler(ajaxLogoutSuccessHandler())
.permitAll()
.and()
.headers()
.frameOptions()
.disable()
.and()
.authorizeRequests()
.antMatchers("/api/profile-info").permitAll()
.antMatchers("/api/**").authenticated()
.antMatchers("/websocket/tracker").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/websocket/**").permitAll()
.antMatchers("/management/health").permitAll()
.antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/v2/api-docs/**").permitAll()
.antMatchers("/swagger-resources/configuration/ui").permitAll()
.antMatchers("/swagger-ui/index.html").hasAuthority(AuthoritiesConstants.ADMIN);
}
#Bean
public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
return new SecurityEvaluationContextExtension();
}
}
application.yml
security:
basic:
enabled: false
oauth2:
client:
access-token-uri: https://dev-800787.oktapreview.com/oauth2/ausb3ecnmsz8Ucjqw0h7/v1/token
user-authorization-uri: https://dev-800787.oktapreview.com/oauth2/ausb3ecnmsz8Ucjqw0h7/v1/authorize
client-id: <okta-client-id>
client-secret: <okta-client-secret>
client-authentication-scheme: form
scope: openid profile email
resource:
filter-order: 3
user-info-uri: https://dev-800787.oktapreview.com/oauth2/ausb3ecnmsz8Ucjqw0h7/v1/userinfo
token-info-uri: https://dev-800787.oktapreview.com/oauth2/ausb3ecnmsz8Ucjqw0h7/v1/introspect
prefer-token-info: false
server:
session:
cookie:
http-only: true
Matt's answer point me to the right direction, thanks!
and here is my current working configuration:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
#Configuration
#EnableResourceServer
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class OAuth2AuthenticationConfiguration extends ResourceServerConfigurerAdapter {
#Bean
public RequestMatcher resources() {
return new RequestHeaderRequestMatcher("Authorization");
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.requestMatcher(resources())
.authorizeRequests()
.anyRequest().authenticated();
}
}
This answer was helpful too, thanks.
You need to use Spring Security OAuth's #EnableResourceServer for this functionality. If you're using Okta, you can also try using its Spring Boot Starter.
I am struggling to do this simple thing in Spring: Redirecting to the Login page when the access token expires.
I have:
a Edge Server (Zuul) for routing.
a OAuth2 authorization / authentication server.
a Resource Server that serves static files.
For specific security reasons I do not want any refresh token, when my TOKEN expires I want the user to login again.
In my understanding the Resource Server should be the one handling this mechanism (correct me if I am wrong).
I have been trying different unsuccessful approaches:
Adding a filter After OAuth2AuthenticationProcessingFilter and check for Token validity and sendRedirect
Adding a custom OAuth2AuthenticationEntryPoint to my application (extending ResourceServerConfigurerAdapter)
Adding .exceptionHandling() with a custom handler / entry point.
Below my security configurations.
Auth Server Security Config:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerOAuthConfig extends AuthorizationServerConfigurerAdapter {
....
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(this.authenticationManager)
.accessTokenConverter(accessTokenConverter())
.approvalStore(approvalStore())
.authorizationCodeServices(authorizationCodeServices())
.tokenStore(tokenStore());
}
/**
* Configure the /check_token endpoint.
* This end point will be accessible for the resource servers to verify the token validity
* #param securityConfigurer
* #throws Exception
*/
#Override
public void configure(AuthorizationServerSecurityConfigurer securityConfigurer) throws Exception {
securityConfigurer
.tokenKeyAccess("permitAll()")
.checkTokenAccess("hasAuthority('ROLE_TRUSTED_CLIENT')");
}
}
#Configuration
#Order(-20)
public class AuthorizationServerSecurityConfig extends WebSecurityConfigurerAdapter {
private static final Logger log = LoggerFactory.getLogger(AuthorizationServerSecurityConfig.class);
#Autowired
private DataSource oauthDataSource;
#Autowired
private AuthenticationFailureHandler eventAuthenticationFailureHandler;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth) throws UserDetailsException {
try {
log.debug("Updating AuthenticationManagerBuilder to use userDetailService with a BCryptPasswordEncoder");
auth.userDetailsService(userDetailsService()).passwordEncoder(new BCryptPasswordEncoder());
} catch (Exception e) {
throw new UserDetailsException(e);
}
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
public UserDetailsService userDetailsService() {
return this.userDetailsService;
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
log.debug("Updating HttpSecurity configuration");
// #formatter:off
http
.requestMatchers()
.antMatchers("/login*", "/login?error=true", "/oauth/authorize", "/oauth/confirm_access")
.and()
.authorizeRequests()
.antMatchers("/login*", "/oauth/authorize", "/oauth/confirm_access").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.failureUrl("/login?error=true")
.failureHandler(eventAuthenticationFailureHandler);
// #formatter:on
}
Its application.yml
server:
port: 9999
contextPath: /uaa
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
security:
user:
password: password
spring:
datasource_oauth:
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/oauth2_server
username: abc
password: 123
jpa:
ddl-create: true
timeleaf:
cache: false
prefix: classpath:/templates
logging:
level:
org.springframework.security: DEBUG
authentication:
attempts:
maximum: 3
Resource Server Security Config - UPDATED:
#EnableResourceServer
#EnableEurekaClient
#SpringBootApplication
public class Application extends ResourceServerConfigurerAdapter {
private CustomAuthenticator customFilter = new CustomAuthenticator();
/**
* Launching Spring Boot
* #param args
*/
public static void main(String[] args) {
SpringApplication.run(Application.class, args); //NOSONAR
}
/**
* Configuring Token converter
* #return
*/
#Bean
public AccessTokenConverter accessTokenConverter() {
return new DefaultAccessTokenConverter();
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.authenticationManager(customFilter);
}
/**
* Configuring HTTP Security
* #param http
* #throws Exception
*/
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.authorizeRequests().antMatchers("/**").authenticated()
.and()
.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.addFilterBefore(customFilter, AbstractPreAuthenticatedProcessingFilter.class);
// #formatter:on
}
protected static class CustomAuthenticator extends OAuth2AuthenticationManager implements Filter {
private static Logger logger = LoggerFactory.getLogger(CustomAuthenticator.class);
private TokenExtractor tokenExtractor = new BearerTokenExtractor();
private AuthenticationManager authenticationManager;
private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new OAuth2AuthenticationDetailsSource();
private boolean inError = false;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
try {
return super.authenticate(authentication);
}
catch (Exception e) {
inError = true;
return new CustomAuthentication(authentication.getPrincipal(), authentication.getCredentials());
}
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
logger.debug("Token Error redirecting to Login page");
if(this.inError) {
logger.debug("In error");
RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
final HttpServletRequest req = (HttpServletRequest) request;
final HttpServletResponse res = (HttpServletResponse) response;
redirectStrategy.sendRedirect(req, res, "http://localhost:8765/login");
this.inError = false;
return;
} else {
filterChain.doFilter(request, response);
}
}
#Override
public void destroy() {
}
#Override
public void init(FilterConfig arg0) throws ServletException {
}
#SuppressWarnings("serial")
protected static class CustomAuthentication extends PreAuthenticatedAuthenticationToken {
public CustomAuthentication(Object principal, Object credentials) {
super(principal, credentials);
}
}
}
}
Its application.yml:
server:
port: 0
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
security:
oauth2:
resource:
userInfoUri: http://localhost:9999/uaa/user
logging:
level:
org.springframework.security: DEBUG
Edge Server Configuration:
#SpringBootApplication
#EnableEurekaClient
#EnableZuulProxy
#EnableOAuth2Sso
public class EdgeServerApplication extends WebSecurityConfigurerAdapter {
/**
* Configuring Spring security
* #return
*/
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http.logout()
.and()
.authorizeRequests().antMatchers("/**").authenticated()
.and()
.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
// #formatter:on
}
/**
* Launching Spring Boot
* #param args
*/
public static void main(String[] args) {
SpringApplication.run(EdgeServerApplication.class, args); //NOSONAR
}
}
Its application.yml config:
server:
port: 8765
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
security:
user:
password: none
oauth2:
client:
accessTokenUri: http://localhost:9999/uaa/oauth/token
userAuthorizationUri: http://localhost:9999/uaa/oauth/authorize
clientId: spa
clientSecret: spasecret
resource:
userInfoUri: http://localhost:9999/uaa/user
zuul:
debug:
request: true
routes:
authorization-server:
path: /uaa/**
stripPrefix: false
fast-funds-service:
path: /**
logging:
level:
org.springframework.security: DEBUG
Thanks for the help
Try setting the validity of the refresh token to 0 or 1 seconds.
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("javadeveloperzone")
.secret("secret")
.accessTokenValiditySeconds(2000) // expire time for access token
.refreshTokenValiditySeconds(-1) // expire time for refresh token
.scopes("read", "write") // scope related to resource server
.authorizedGrantTypes("password", "refresh_token"); // grant type
https://javadeveloperzone.com/spring-security/spring-security-oauth2-success-or-failed-event-listener/#22_SecurityOAuth2Configuration
Based on your comment you can add this:
http.exceptionHandling()
.authenticationEntryPoint(unauthorizedEntryPoint())
#Bean
public AuthenticationEntryPoint unauthorizedEntryPoint() {
return (request, response, authException) -> response.response.sendRedirect("/login"));
}
I'm in the process of building separate resource server and authhorization server. For now i'm using the user-info-uri in resource server to extract the Principal from the authorization server matching the access-token, with config:
spring:
oauth2:
resource:
userInfoUri: http://localhost:9999/uaa/user
In the resource server I have protected endpoints, based on role, as follows:
http
.authorizeRequests()
.antMatchers("/invoices/**").hasRole("END_USER")
.anyRequest().authenticated();
When I manually access the user-info-uri, I can see that the the authorities contain:
"authority": "ROLE_END_USER"
But when I try to access the /invoices resource I get an Access-Denied exception, and in the log I see:
OAuth2Authentication#bc5074a8: Principal: my-login; Credentials: [PROTECTED]; Authenticated: true; Details: remoteAddress=0:0:0:0:0:0:0:1, tokenType=BearertokenValue=<TOKEN>; Granted Authorities: ROLE_USER
Authoriteis = "ROLE_USER". Where does that come from, should'nt it be "ROLE_END_USER" at this point also?
I've seen implementations using a shared database for the storage of tokens, is that really necessary for what I want to achive?
Resource Server:
#SpringBootApplication
#EnableOAuth2Resource
public class EndUserResourceServiceApplication extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/invoices/**").hasRole("END_USER")
.anyRequest().authenticated();
}
public static void main(String[] args) {
SpringApplication.run(EndUserResourceServiceApplication.class, args);
}
}
Auth Server:
#SpringBootApplication
#RestController
#EnableResourceServer
public class ApiAuthServerApplication extends ResourceServerConfigurerAdapter {
#Configuration
#EnableWebSecurity
#Order(-10)
protected static class LoginConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailsService userDetailsService;
#Bean(name = "authenticationManagerBean")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin().permitAll()
.and()
.requestMatchers().antMatchers("/login", "/oauth/authorize", "/oauth/confirm_access")
.and()
.authorizeRequests().anyRequest().authenticated();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
#Configuration
#EnableAuthorizationServer
protected static class OAuth2Config extends AuthorizationServerConfigurerAdapter {
#Autowired
private TokenStore tokenStore;
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Autowired
private CustomUserDetailsService userDetailsService;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(this.authenticationManager)
.userDetailsService(this.userDetailsService)
.tokenStore(this.tokenStore);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("my-client")
.secret("our_s3cret")
.authorities("ROLE_CLIENT")
.authorizedGrantTypes("implicit", "password", "refresh_token")
.redirectUris("http://anywhere")
.scopes("read")
.autoApprove(true);
}
#Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
}
public static void main(String[] args) {
SpringApplication.run(ApiAuthServerApplication.class, args);
}
#RequestMapping("/user")
public Principal user(Principal user) {
return user;
}
}
Summary:
Can I use user-info-uri for validating access-tokens and to use "hasRole"
Is it necessary to use shared database for token storage when using separate resource server and authorization server?
I ended up using a shared token store, which works well at the moment
I am trying to use Spring-Security 4.0.0.M1 with Spring-Boot 1.0.2.RELEASE with H2 and Spring-data-jpa.1.5.2. I am able to create a user in the SecurityConfig.configure method, but when I extract this out to its own class, I get a nullpointer exception. I step into JdbcDaoSupport.java and see that the getJdbcTemplate() returns a null jdbcTemplate.
Here is my config:
#Configuration
#EnableWebMvcSecurity
#EnableGlobalMethodSecurity(prePostEnabled = false)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private DataSource dataSource;
#Autowired
UserService userService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers( "/resources/**" ).permitAll()
.antMatchers( "/css/**" ).permitAll();
http
.formLogin().failureUrl( "/login?error" )
.defaultSuccessUrl( "/" )
.loginPage( "/login" )
.permitAll()
.and()
.logout().logoutRequestMatcher( new AntPathRequestMatcher( "/logout" ) ).logoutSuccessUrl( "/login" )
.permitAll();
http
.authorizeRequests().anyRequest().authenticated();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.jdbcAuthentication()
.dataSource( dataSource );
}
#Bean
#Override
public AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManagerBean();
}
}
My main configuration class has the datasource defined, which I know to work:
#Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder().setType( EmbeddedDatabaseType.H2 ).setName( "ofac" )
.addScript( "classpath:h2.sql" ).build();
}
I have a User Service for adding users as such:
#Component
public class UserService {
#Autowired
private AuthenticationManagerBuilder authenticationManagerBuilder;
#Autowired
private javax.sql.DataSource dataSource;
#PostConstruct()
public void initUsers() throws Exception {
addNewUser( "admin", "password", "ADMIN" );
}
public void addNewUser(String username, String plainPassword, String role) {
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add( new SimpleGrantedAuthority( role ) );
PasswordEncoder encoder = new BCryptPasswordEncoder();
UserDetails hashedUser = new User( username, encoder.encode( plainPassword ), authorities );
JdbcUserDetailsManager userDetailsService = (JdbcUserDetailsManager) authenticationManagerBuilder.getDefaultUserDetailsService();
try {
if ( userDetailsService.userExists( hashedUser.getUsername() ) ) {
userDetailsService.deleteUser( hashedUser.getUsername() );
}
// and finally, create the user
userDetailsService.createUser( hashedUser );
} catch ( Exception ex ) {
ex.printStackTrace();
}
}
}
I have a breakpoint on the UserService.initUsers and when the line:
authenticationManagerBuilder.jdbcAuthentication().getUserDetailsService().userExists( hashedUser.getUsername() )
is invoked, I can see that authenticationManagerBuilder.jdbcAuthentication() returns a null jdbcTemplate. All the online documentation seems to indicate this would work, but it does not seem to wire up everything as I am expecting.
Does anyone know what might be wrong?
UPDATE:
I changed the project so I no longer have the SecurityConfig, but instead to have a:
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
#Configuration
public class ApplicationSecurity extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers( "/resources/**" ).permitAll()
.antMatchers( "/css/**" ).permitAll();
http
.formLogin().failureUrl( "/login?error" )
.defaultSuccessUrl( "/" )
.loginPage( "/login" )
.permitAll()
.and()
.logout().logoutRequestMatcher( new AntPathRequestMatcher( "/logout" ) ).logoutSuccessUrl( "/login" )
.permitAll();
http
.authorizeRequests().anyRequest().authenticated();
}
}
and a:
#Order(Ordered.HIGHEST_PRECEDENCE)
#Configuration
public class AuthenticationSecurity extends GlobalAuthenticationConfigurerAdapter {
#Autowired
private DataSource dataSource;
#Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth
.jdbcAuthentication()
.dataSource( dataSource );
}
}
But in my UserService, I get a null userDetailsService here:
JdbcUserDetailsManager userDetailsService = (JdbcUserDetailsManager) authenticationManagerBuilder.getDefaultUserDetailsService();
I have not figured out how to actually create a user after startup. I thought it was with a UserDetailsService, but that doesn't provide the functionality. I thought maybe a JdbcUserDetailsManager is needed, but so far I haven't been able to wire one up that works.
The AuthenticationManagerBuilder may not have been fully initialized when it is injected into your object. Spring Security uses various markers to signal the initialization order, e.g. you could try extending GlobalAuthenticationConfigurerAdapter like in the method security sample.