I have a application use spring security configure xml integrate with spring MVC.
My spring security configure xml:
<http pattern="/admin/**" authentication-manager-ref="adminAuthenticationManager">
<intercept-url pattern="/" access="permitAll" />
<form-login login-page="/login" username-parameter="userEmail"
login-processing-url="/admin/j_spring_security_check"
authentication-success-handler-ref="customAuthenticationSuccessHandler"
password-parameter="password" authentication-failure-url="/admin/login?error" />
<logout logout-url="/admin/j_spring_security_logout"
logout-success-url="/admin/login?logout" />
<remember-me key="uniqueAndSecret" user-service-ref="customUserDetailsService"
token-validity-seconds="7776000" />
<csrf disabled="true" />
</http>
<http authentication-manager-ref="userAuthenticationManager">
<intercept-url pattern="/" access="permitAll" />
<form-login login-page="/login" username-parameter="userEmail"
login-processing-url="/j_spring_security_check" password-parameter="password"
authentication-success-handler-ref="customAuthenticationSuccessHandler"
authentication-failure-url="/login?error" />
<logout logout-url="/j_spring_security_logout"
logout-success-url="/login?logout" />
<remember-me key="uniqueAndSecret" user-service-ref="customAdminDetailsService"
token-validity-seconds="7776000" />
<csrf disabled="true" />
</http>
<authentication-manager erase-credentials="false"
id="userAuthenticationManager">
<authentication-provider user-service-ref="customUserDetailsService" />
</authentication-manager>
<authentication-manager erase-credentials="false"
id="adminAuthenticationManager">
<authentication-provider user-service-ref="customAdminDetailsService" />
</authentication-manager>
<beans:bean id="customUserDetailsService"
class="com.service.impl.UserDetailServiceImpl" />
<beans:bean id="customAdminDetailsService"
class="com.service.impl.AdminDetailServiceImpl" />
My custom UserServiceDetailImpl file:
#Service
public class UserDetailServiceImpl implements UserDetailsService {
#Autowired
private UserService userService;
#Override
public UserDetails loadUserByUsername(String email) {
try {
User user = userService.getUserByEmail(email);
if (user != null) {
return new org.springframework.security.core.userdetails.User(user.getUserEmail(), user.getPassword(),
true, true, true, true, getGrantedAuthorities(user));
} else {
throw new UsernameNotFoundException(email);
}
} catch (Exception e) {
throw new UsernameNotFoundException(email);
}
}
private List<GrantedAuthority> getGrantedAuthorities(Object generalUser) {
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
if (generalUser instanceof User) {
authorities.add(new SimpleGrantedAuthority(CommonConstant.Role.USER.toString()));
}
return authorities;
}
}
Now How can I change it to spring configure by annotation?
My WebsercurityConfig file when I changed to use annotation in spring security:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailServiceImpl userDetailService;
#Autowired
AdminDetailServiceImpl adminDetailService;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailService);
auth.userDetailsService(adminDetailService);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/").permitAll()
.and().formLogin().loginPage("/login")
.failureUrl("/login?error").loginProcessingUrl("/j_spring_security_check")
.successHandler(loginSuccessHanlder).failureUrl("/login?error=true")
.usernameParameter("userEmail").passwordParameter("password")
.and().logout().logoutUrl("/j_spring_security_logout").logoutSuccessUrl("/login?logout")
.and().userDetailsService(userDetailService)
.rememberMe().key("uniqueAndSecret")
.tokenValiditySeconds(33343)
.and()
.authorizeRequests().antMatchers("/admin/**").permitAll()
.and().formLogin().loginPage("/login")
.failureUrl("/admin/login?error").loginProcessingUrl("/admin/j_spring_security_check")
.successHandler(loginSuccessHanlder).failureUrl("/admin/login?error=true")
.usernameParameter("userEmail").passwordParameter("password")
.and().logout().logoutUrl("/admin/j_spring_security_logout").logoutSuccessUrl("/admin/login?logout")
.and().userDetailsService(userDetailService)
.rememberMe().key("uniqueAndSecret")
.tokenValiditySeconds(33343);
}
}
Documentation should help you
#Configuration
#EnableWebSecurity
public class SecurityConfig {
#Autowired
private UserDetailServiceImpl userDetailService;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailService)
auth.userDetailsService(customAdminDetailsService());
}
#Bean
UserDetailsService customAdminDetailsService() {
/* custom UserDetailsService code here */
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatchers("/").permitAll()
.and()
.formLogin().loginPage("/login").failureUrl("/login?error")
.createLoginProcessingUrlMatcher("/j_spring_security_check")
.successHandler(customAuthenticationSuccessHandler()).failureUrl("/login?error").usernameParameter("userEmail").passwordParameter("password").logout().invalidateHttpSession(false)
.logoutUrl("/custom-logout").logoutSuccessUrl("/logout-success")
.userDetailsService(customUserDetailsService()).rememberMe().key("uniqueAndSecret").userDetailsService(customUserDetailsService()).tokenValiditySeconds(33343)
.csrf().disable()
.and()
.antMatcher("/admin/**")
/* your admin configurations */
}
}
Related
My spring boot (2.2.4) application is using siteminder as authentication provider. Upon successful authentication siteminder sends user details (Roles) in Http header. Inside the application I need to use spring security for role based authorization. Here is reference documentation I am following -
https://docs.spring.io/spring-security/site/docs/5.0.x/reference/html/preauth.html
My spring-security.xml will look this.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
https://www.springframework.org/schema/security/spring-security.xsd">
<security:http>
<!-- Additional http configuration omitted -->
<security:csrf disabled="true"/>
<security:intercept-url pattern="/**" access="hasRole('ROLE_ABC')"/>
<security:custom-filter position="PRE_AUTH_FILTER" ref="siteminderFilter" />
</security:http>
<bean id="siteminderFilter" class="org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter">
<property name="principalRequestHeader" value="SM_USER"/>
<property name="authenticationManager" ref="authenticationManager" />
</bean>
<bean id="preauthAuthProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
<property name="preAuthenticatedUserDetailsService">
<bean id="userDetailsServiceWrapper"
class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
<property name="userDetailsService" ref="userDetailsService"/>
</bean>
</property>
</bean>
<bean id="userDetailsService" class="com.mycode.service.SCPMUserDetailsService"/>
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider ref="preauthAuthProvider" />
</security:authentication-manager>
</beans>
How do I create a Java config file which will incorporate above security for Spring boot application.
I started something like this and need to inject all the pieces in above xml file.
#EnableWebSecurity
#Order(SecurityProperties.BASIC_AUTH_ORDER - 10)
public class SecurityConfigurer extends WebSecurityConfigurerAdapter {
#Autowired
private MyUserDetailsService myUserDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()// Disable for GraphIQL to work locally
.antMatcher("/**")
.authorizeRequests()
.antMatchers("/healthcheck/**").permitAll()
.antMatchers("/scpm/**").permitAll()
.antMatchers("/approvers/**").hasRole("APPROVER")
.antMatchers("/departments/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and().httpBasic();
}
}
Spring Security reference document does not provide Java Code for SiteMinder integration. It just provides xml configuration for non-spring boot applications. Here is the Java code which can be used in any Spring Boot Application.
#EnableWebSecurity
#Configuration
#Order(SecurityProperties.BASIC_AUTH_ORDER - 10)
public class SecurityConfigurer extends WebSecurityConfigurerAdapter {
#Autowired
private MyUserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
auth.authenticationProvider(preAuthenticatedAuthenticationProvider());
}
#Bean
public RequestHeaderAuthenticationFilter siteminderFilter() throws Exception {
RequestHeaderAuthenticationFilter filter = new RequestHeaderAuthenticationFilter();
filter.setPrincipalRequestHeader("SM_USER");
filter.setAuthenticationManager(this.authenticationManager());
return filter;
}
#Bean
public PreAuthenticatedAuthenticationProvider preAuthenticatedAuthenticationProvider() {
PreAuthenticatedAuthenticationProvider preauthAuthProvider = new PreAuthenticatedAuthenticationProvider();
preauthAuthProvider.setPreAuthenticatedUserDetailsService(userDetailsByNameServiceWrapper());
return preauthAuthProvider;
}
#Bean
public UserDetailsByNameServiceWrapper userDetailsByNameServiceWrapper() {
UserDetailsByNameServiceWrapper userDetailsServiceWrapper = new UserDetailsByNameServiceWrapper();
userDetailsServiceWrapper.setUserDetailsService(userDetailsService);
return userDetailsServiceWrapper;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilter(siteminderFilter())
.csrf().disable()// Disable for GraphIQL to work locally
.antMatcher("/**")
.authorizeRequests()
.antMatchers("/healthcheck/**").permitAll()
.antMatchers("/approvers/**").hasRole("APPROVER")
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and().httpBasic();
}
}
Here is custom UserDetailsService.
#Service
public class MyUserDetailsService implements UserDetailsService {
private Logger logger = LoggerFactory.getLogger(SCPMUserDetailsService.class);
#Autowired
private HttpServletRequest request;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{
HttpServletRequest request =
((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
String csgroups = "";
Collection<GrantedAuthority> auth = new ArrayList<>();
try {
username = request.getHeader("SM_USER");
} catch (Exception ex){
logger.trace("SM_USER not found in Request Http Headers");
ex.printStackTrace();
}
try {
csgroups = request.getHeader("ROLES");;
} catch (Exception ex){
logger.trace("CSGROUPS not found in Request Http Headers");
ex.printStackTrace();
}
//Roles
if(csgroups != null) {
StringTokenizer st = new StringTokenizer(csgroups, "^");
while (st.hasMoreElements()) {
GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_" + st.nextToken());
}
}
return new User(username, "", true, true, true, true,auth);
}
#Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
we have a spring MVC application and I think there is some misconfiguration about session or cookies management basically we have that config about resources, session config and security
XML config:
<security:http auto-config=
"false" use-expressions="true"
disable-url-rewriting="true" entry-point-ref="authenticationEntryPoint"
create-session="ifRequired">
<security:headers>
<security:frame-options policy="SAMEORIGIN"/>
</security:headers>
<security:csrf disabled="true"/>
<security:custom-filter ref="sessionFilter" before="SESSION_MANAGEMENT_FILTER" />
<security:custom-filter before="PRE_AUTH_FILTER" ref="openIdConnectAuthenticationFilter" />
<security:intercept-url pattern="/resources/**" access="permitAll()"/>
...
</security:http>
Session Filter:
public class SessionFilter implements Filter {
private boolean httpOnly=false;
private boolean secure=false;
public SessionFilter(boolean httpOnly, boolean secure) {
this.httpOnly = httpOnly;
this.secure = secure;
}
public SessionFilter() {
}
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(
ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
Cookie[] allCookies = req.getCookies();
if (allCookies != null && !"self-health-check".equals(req.getHeader("User-Agent"))) {
Cookie session =
Arrays.stream(allCookies).filter(x -> x.getName().equals("JSESSIONID"))
.findFirst().orElse(null);
if (session != null) {
session.setHttpOnly(httpOnly);
session.setSecure(secure);
res.addCookie(session);
}
}
chain.doFilter(req, res);
}
#Override
public void destroy() {
}
}
And the client is asking this question; why there are a lot of endpoints of sessions?
I don't know it is normal or not.
help, please!
I found the solution after some month. The problem is not about my spring MVC config, it is about web.xml config. we needed to add a tracking-mode setting
<session-config>
<tracking-mode>COOKIE</tracking-mode>
</session-config>
Following is my sample Spring Security configuration.
I want all /api to return HTTP 401 code instead of redirecting 302 to login page.
Also I want to keep the redirect feature for old web pages.
<security:http auto-config='true' use-expressions="true" >
<security:intercept-url pattern="/api*" access="hasRole('USER')" />
<security:intercept-url pattern="/oldweb*" access="hasRole('USER')" />
<security:form-login login-page="/login.jsp" authentication-failure-url="/login.jsp?login_error=1" default-target-url="/home"/>
</security:http>
I came to more simpler solution.
In Spring Boot and Java config you just have to register additional entry point in addition to default one. And because all your rest-services resides in "/api" name space, you could use AntPathRequestMatcher("/api/**") to match necessary requests.
So, final solution is:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling()
//Actually Spring already configures default AuthenticationEntryPoint - LoginUrlAuthenticationEntryPoint
//This one is REST-specific addition to default one, that is based on PathRequest
.defaultAuthenticationEntryPointFor(getRestAuthenticationEntryPoint(), new AntPathRequestMatcher("/api/**"));
}
private AuthenticationEntryPoint getRestAuthenticationEntryPoint() {
return new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED);
}
}
You need to have a custom authentication entry point.
public class CustomEntryPoint extends LoginUrlAuthenticationEntryPoint {
private static final String XML_HTTP_REQUEST = "XMLHttpRequest";
private static final String X_REQUESTED_WITH = "X-Requested-With";
public CustomEntryPoint(String loginFormUrl) {
super(loginFormUrl);
}
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception)
throws IOException, ServletException {
if (XML_HTTP_REQUEST.equals(request.getHeader(X_REQUESTED_WITH))) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
} else {
super.commence(request, response, exception);
}
}
}
Finally change your config to this:
<security:http auto-config='true' use-expressions="true" entry-point-ref="customEntryPoint">
<security:intercept-url pattern="/api*" access="hasRole('USER')" />
<security:intercept-url pattern="/oldweb*" access="hasRole('USER')" />
<security:form-login login-page="/login.jsp" authentication-failure-url="/login.jsp?login_error=1" default-target-url="/home"/>
<beans:bean id="customEntryPoint" class="CustomEntryPoint">
<beans:constructor-arg value="/login"/>
</beans:bean>
</security:http>
When text/html is not acceptable, return 401.
#EnableWebSecurity
public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(final HttpSecurity http) throws Exception {
// ...
final RequestMatcher matcher = new NegatedRequestMatcher(
new MediaTypeRequestMatcher(MediaType.TEXT_HTML));
http.exceptionHandling()
.defaultAuthenticationEntryPointFor(
new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED), matcher);
}
}
I am currently moving from an xml-based configuration of spring security to a java based one. I need to setup a custom WebAuthenticationDetails via Java. Is there a way to do that? In XML, I would just set that authenticationDetailsSource of the UsernamePasswordAuthenticationFilter. Relevant sample below
<http entry-point-ref="loginUrlAuthenticationEntryPoint">
<custom-filter ref="rememberMeFilter" position="REMEMBER_ME_FILTER"/>
<custom-filter ref="loginFilter" position="FORM_LOGIN_FILTER"/>
<intercept-url pattern="/access/**" access="ROLE_USER" />
<csrf/>
<access-denied-handler error-page="/login" />
<logout logout-success-url="/login?logout" />
</http>
<beans:bean id="myWebAuthDetails"
class="com.auth.CustomWebAuthenticationDetailsSource">
</beans:bean>
<beans:bean id="loginFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<beans:property name="authenticationManager" ref="authenticationManager" />
<beans:property name="rememberMeServices" ref="rememberMeServices" />
<beans:property name="usernameParameter" value="username" />
<beans:property name="passwordParameter" value="password" />
<beans:property name="sessionAuthenticationStrategy" ref="sas" />
<beans:property name="allowSessionCreation" value="true" />
<beans:property name="authenticationDetailsSource" ref="myWebAuthDetails" />
<beans:property name="authenticationFailureHandler" ref="failureHandler" />
<beans:property name="authenticationSuccessHandler" ref="successHandler" />
<beans:property name="filterProcessesUrl" value="/processlogin" />
</beans:bean>
Please find the configuration below. I have mapped your xml configuration to Java config.
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/", "/home").permitAll()
.anyRequest().authenticated().and().formLogin()
.authenticationDetailsSource(authenticationDetailsSource())
.successHandler(authenticationSuccessHandler())
.failureHandler(failureHandler()).loginPage("/login")
.usernameParameter("usernameCustom")
.passwordParameter("passwordCustom").permitAll().and().logout()
.permitAll().and().rememberMe()
.rememberMeServices(rememberMeServices());
}
private AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> authenticationDetailsSource() {
return new AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails>() {
#Override
public WebAuthenticationDetails buildDetails(
HttpServletRequest request) {
return new WebAuthenticationDetails(request);
}
};
}
#Bean
RememberMeServices rememberMeServices() {
RememberMeServices rememberMeServices = new RememberMeServices() {
#Override
public void loginSuccess(HttpServletRequest arg0,
HttpServletResponse arg1, Authentication arg2) {
}
#Override
public void loginFail(HttpServletRequest arg0,
HttpServletResponse arg1) {
}
#Override
public Authentication autoLogin(HttpServletRequest arg0,
HttpServletResponse arg1) {
return null;
}
};
return rememberMeServices;
}
#Bean
AuthenticationFailureHandler failureHandler() {
return new AuthenticationFailureHandler() {
#Override
public void onAuthenticationFailure(HttpServletRequest req,
HttpServletResponse res, AuthenticationException arg2)
throws IOException, ServletException {
req.setAttribute("error", "forward");
req.getRequestDispatcher("/homedefault").forward(req, res);
}
};
}
#Bean
AuthenticationSuccessHandler authenticationSuccessHandler() {
return new AuthenticationSuccessHandler() {
#Override
public void onAuthenticationSuccess(HttpServletRequest req,
HttpServletResponse res, Authentication arg2)
throws IOException, ServletException {
res.sendRedirect("homedefault");
}
};
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication().withUser("user").password("password")
.roles("USER");
}
}
I would have one question regarding the configuration of spring-security-oauth2 2.0.7 please.
I am doing the Authentication using LDAP via a GlobalAuthenticationConfigurerAdapter:
#SpringBootApplication
#Controller
#SessionAttributes("authorizationRequest")
public class AuthorizationServer extends WebMvcConfigurerAdapter {
public static void main(String[] args) {
SpringApplication.run(AuthorizationServer.class, args);
}
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
registry.addViewController("/oauth/confirm_access").setViewName("authorize");
}
#Configuration
public static class JwtConfiguration {
#Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
KeyPair keyPair = new KeyStoreKeyFactory(
new ClassPathResource("keystore.jks"), "foobar".toCharArray())
.getKeyPair("test");
converter.setKeyPair(keyPair);
return converter;
}
#Bean
public JwtTokenStore jwtTokenStore(){
return new JwtTokenStore(jwtAccessTokenConverter());
}
}
#Configuration
#EnableAuthorizationServer
public static class OAuth2Config extends AuthorizationServerConfigurerAdapter implements EnvironmentAware {
private static final String ENV_OAUTH = "authentication.oauth.";
private static final String PROP_CLIENTID = "clientid";
private static final String PROP_SECRET = "secret";
private static final String PROP_TOKEN_VALIDITY_SECONDS = "tokenValidityInSeconds";
private RelaxedPropertyResolver propertyResolver;
#Inject
private AuthenticationManager authenticationManager;
#Inject
private JwtAccessTokenConverter jwtAccessTokenConverter;
#Inject
private JwtTokenStore jwtTokenStore;
#Inject
private UserDetailsService userDetailsService;
#Override
public void setEnvironment(Environment environment) {
this.propertyResolver = new RelaxedPropertyResolver(environment, ENV_OAUTH);
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setSupportRefreshToken(true);
tokenServices.setTokenStore(jwtTokenStore);
tokenServices.setAuthenticationManager(authenticationManager);
return tokenServices;
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager).tokenStore(jwtTokenStore).accessTokenConverter(
jwtAccessTokenConverter).userDetailsService(userDetailsService);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer)
throws Exception {
oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess(
"isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient(propertyResolver.getProperty(PROP_CLIENTID))
.scopes("read", "write")
.authorities(AuthoritiesConstants.ADMIN, AuthoritiesConstants.USER)
.authorizedGrantTypes("authorization_code", "refresh_token", "password")
.secret(propertyResolver.getProperty(PROP_SECRET))
.accessTokenValiditySeconds(propertyResolver.getProperty(PROP_TOKEN_VALIDITY_SECONDS, Integer.class, 1800));
}
}
#Configuration
#Order(-10)
protected static class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin().loginPage("/login").permitAll()
.and()
.requestMatchers().antMatchers("/login", "/oauth/authorize", "/oauth/confirm_access")
.and()
.authorizeRequests().anyRequest().authenticated();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
#Override
public UserDetailsService userDetailsServiceBean() throws Exception {
return super.userDetailsServiceBean();
}
}
#Configuration
protected static class AuthenticationConfiguration extends
GlobalAuthenticationConfigurerAdapter {
#Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.userDnPatterns("uid={0},ou=people")
.groupSearchBase("ou=groups")
.contextSource().ldif("classpath:test-server.ldif");
}
}
}
While the refresh token works fine with the release 2.0.6 of spring-security-oauth2, it does not work anymore with the version 2.0.7.
As read here, one should set the AuthenticationManager to be used when trying to get a new access token during the refresh.
As far as I understand, this has something to do with the following change of spring-security-oauth2.
I unfortunately did not manage to set it up properly.
org.springframework.security.oauth2.provider.token.DefaultTokenServices#setAuthenticationManager
is called and gets an AuthenticationManager injected. I an not sure I understand how the LdapUserDetailsService is then going to be injected. The only thing I see is that the PreAuthenticatedAuthenticationProvider is going to be called while trying to re-authenticate the user during the token refresh call.
Can anyone advise me on how to do it please?
ps: The exception I am getting is the following:
p.PreAuthenticatedAuthenticationProvider : PreAuthenticated authentication request: org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken#5775: Principal: org.springframework.security.authentication.UsernamePasswordAuthenticationToken#441d5545: Principal: bob; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_USER
o.s.s.o.provider.endpoint.TokenEndpoint : Handling error: IllegalStateException, UserDetailsService is required.
I had a similar issue when I was implementing a a OAuth2 server with JWT tokens with a custom AuthenticationProvider instead of a UserDetailsService implementation to solve login authentications.
But lately I found that the error Spring raises is correct if you want the refresh_token working correctly. For an AuthenticationProvider implementation is impossible to refresh a token with a refresh_token, because in that kind of implementation you have to resolve if the password is correct, but the refresh token doesn't have that information. However, UserDetailsService is agnostic of the password.
The version 2.0.6 of spring-security-oauth2 works because never checks the user grants, just checks if the refresh token is valid (signed with the private key), but, if the user was deleted from the system after a first login, with a refresh token the deleted user will have infinite time access to your system, that is a big security issue.
Take a look to the issue I reported with this: https://github.com/spring-projects/spring-security-oauth/issues/813
What you need for the OAuth piece is to create an LdapUserDetailsService with the same query as you authenticator and inject it into the AuthorizationServerEndpointsConfigurer. I don't think there's any support for creating a UserDetailService in #Configuration style (might be worth opening a ticket for that in JIRA), but it looks like you can do it in XML.
As advised by Dave Syer, I created a custom LdapUserDetailsService.
The working solution can be found under the following tag.
Application Context
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<context:property-placeholder location="application.yml"/>
<bean id="contextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
<constructor-arg value="${authentication.ldap.url}" />
</bean>
<bean id="userSearch" class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
<constructor-arg index="0" value="${authentication.ldap.userSearchBase}" />
<constructor-arg index="1" value="uid={0}" />
<constructor-arg index="2" ref="contextSource"/>
</bean>
<bean id="ldapAuthoritiesPopulator" class="org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator">
<constructor-arg index="0" ref="contextSource"/>
<constructor-arg index="1" value="${authentication.ldap.groupSearchBase}"/>
<property name="groupSearchFilter" value="${authentication.ldap.groupSearchFilter}"/>
</bean>
<bean id="myUserDetailsService"
class="org.springframework.security.ldap.userdetails.LdapUserDetailsService">
<constructor-arg index="0" ref="userSearch"/>
<constructor-arg index="1" ref="ldapAuthoritiesPopulator"/>
</bean>
</beans>
Properties
authentication:
ldap:
url: ldap://127.0.0.1:33389/dc=springframework,dc=org
userSearchBase:
userDnPatterns: uid={0},ou=people
groupSearchBase: ou=groups
groupSearchFilter: (uniqueMember={0})