Spring Boot: Configure custom MethodSecurityExpressionOperations? - java

I am trouble shooting a spring boot security configuration that I once had working, but now is not recognizing my custom definitions. My goal was to protect all of our Services with method level security in Spring with custom annotations.
When I start the service my CustomMethodSecurityConfig is instantiated and does call createExpressionHandler(), but when I make the request to the service it does not call createSecurityExpressionRoot(...) on my CustomMethodSecurityExpressionHandler, but on the DefaultWebSecurityExpressionHandler.
I appreciate any insights anyone may be able to provide as to why Spring Security is not recognizing my expressions defined in my CustomMethodSecurityExpressionRoot.
Here is a snippet of my GlobalMethodSecurityConfiguration class
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class CustomMethodSecurityConfig extends GlobalMethodSecurityConfiguration {
private final MyService1 myService1;
private final MyService2 myService2;
private final MyService3 myService3;
#Autowired
public CustomMethodSecurityConfig(MyService1 myService1, MyService2 myService2,
MyService3 myService3) {
this.myService1 = myService1;
this.myService2 = myService2;
this.myService3 = myService3;
}
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
CustomMethodSecurityExpressionHandler expressionHandler =
new CustomMethodSecurityExpressionHandler(myService1, myService2, myService3);
expressionHandler.setPermissionEvaluator(permissionEvaluator());
return expressionHandler;
}
}
Here is a snippet of my DefaultMethodSecurityExpressionHandler class
public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler {
private final MyService1 myService1;
private final MyService2 myService2;
private final MyService3 myService3;
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
public CustomMethodSecurityExpressionHandler(MyService1 myService1, MyService2 myService2,
MyService3 myService3) {
this.myService1 = myService1;
this.myService2 = myService2;
this.myService3 = myService3;
}
#Override
protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication,
MethodInvocation invocation) {
CustomMethodSecurityExpressionRoot root = new CustomMethodSecurityExpressionRoot(authentication,
myService1,
myService2,
myService3);
root.setPermissionEvaluator(getPermissionEvaluator());
root.setTrustResolver(this.trustResolver);
root.setRoleHierarchy(getRoleHierarchy());
return root;
}
}
Here is the snippet of my SecurityExpressionRoot, this is where I am defining my SpEL expressions which I use in annotations on my Services. I have only included a simplified, isUser as an example. What these methods do isn't important, but the fact that they are visible.
public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot
implements MethodSecurityExpressionOperations {
private Object filterObject;
private Object returnObject;
private MyService1 myService1;
private MyService2 myService2;
private MyService3 myService3;
public CustomMethodSecurityExpressionRoot(
Authentication authentication,
MyService1 myService1,
MyService2 myService2,
MyService3 myService3) {
super(authentication);
this.myService1 = myService1;
this.myService2 = myService2;
this.myService3 = myService3;
}
#Override
public Object getFilterObject() {
return this.filterObject;
}
#Override
public Object getReturnObject() {
return this.returnObject;
}
#Override
public void setFilterObject(Object obj) {
this.filterObject = obj;
}
#Override
public void setReturnObject(Object obj) {
this.returnObject = obj;
}
#Override
public Object getThis() {
return this;
}
//All custom SpEL methods
public boolean isUser(Long userId) {
SecurityUser user = (SecurityUser) this.getPrincipal();
return user.getUserId() == userId;
}
...
}
And finally here is a snippet of my WebSecurityConfigurerAdapter which is used in tandem, it verifies the external authentication token from our UAA server.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(
prePostEnabled = true,
proxyTargetClass = true)
public class ServiceSecurityConfig extends WebSecurityConfigurerAdapter {
private final TokenCheckService _tokenCheckService;
#Autowired
ServiceSecurityConfig(TokenCheckService tokenCheckService) {
_tokenCheckService = tokenCheckService;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(new TokenAuthenticationProvider(_tokenCheckService));
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(HttpMethod.OPTIONS, "/api/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.anonymous()
.disable()
.csrf()
.disable()
.exceptionHandling()
.authenticationEntryPoint(new UnAuthorizedEntryPoint())
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.anyRequest().authenticated();
http.addFilterBefore(new AuthenticationTokenFilter(), BasicAuthenticationFilter.class);
}
}
Edit:
I seem to think it is an issue with my WebDecisionVoters being overridden during intialization. If I have a breakpoint in the Affirmative constructor
AffirmativeBased(List<AccessDecisionVoter<? extends Object>> decisionVoters)
I can see AffirmativeBased being instantiated with 3 decision voters, one of which is a PreInvocationAuthorizationAdviceVoter, which contains a reference to my expression handler. I believe this is being created by bean instantiation of the methodSecurityInterceptor.
When I continue the breakpoint I again hit the same Affirmative based constructor, but with only one decision voter, a WebExperssionVoter with a reference to an instance of DefaultWebSecurityExpressionHandler. I believe this is being created by bean instantiation of the springSecurityFilterChain.

I was able to resolve this issue by following the steps in Custom SecurityExpression with Service. The issue appears to have been with my autowired services that were separate from security. MyService1, MyService2, and MyService3 causing the issues and removing them allowed security to work.
Any additional services must be set in createSecurityExpressionRoot of the class that extends DefaultMethodSecurityExpressionHandler.
#Override
protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, MethodInvocation invocation) {
CustomMethodSecurityExpressionRoot root = new CustomMethodSecurityExpressionRoot(authentication);
// Other initialization
root.setMyService1(applicationContext.getBean(MyService1.class));
root.setMyService2(applicationContext.getBean(MyService2.class));
root.setMyService3(applicationContext.getBean(MyService3.class));
return root;
}

Related

How to force an order of Spring Security HTTP filters?

I have "main" Spring Security filter added in WebSecurityConfigurerAdapter .configure method:
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
// omitted for brevity....
http.apply(new JwtTokenFilterConfigurer(jwtTokenUtil, objectMapper));
}
}
and the SecurityConfigurerAdapter:
public class JwtTokenFilterConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
// omitted for brevity....
#Override
public void configure(HttpSecurity http) throws Exception {
final JwtTokenFilter customFilter = new JwtTokenFilter(jwtTokenUtil, objectMapper);
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
}
}
There is another HTTP filter(s), eg:
#Component
#Order(Ordered.HIGHEST_PRECEDENCE + 2)
public class WhitelistOriginatedFilter extends OncePerRequestFilter {
// omitted for brevity....
}
Filtering works as expected but this filter is executed before JwtTokenFilter defined in JwtTokenFilterConfigurer.
Question: Is there way how to force that JwtTokenFilter will be executed before all other HTTP filters without need to do not specify #Order in these other filters? (because I need to set the execution order of these filters).
Try this:
Set a breakpoint in FilterChainProxy#doFilter(ServletRequest, ServletResponse), debug and find out the first filter which is actually executed.
Then adjust
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
to
http.addFilterBefore(customFilter, <class of the first executed filter>);
I finally solved the problem by implementing the SecurityConfigurerAdapter and setting the filters in the desired order.
public class HttpFilterConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private final JwtTokenUtil jwtTokenUtil;
private final ObjectMapper objectMapper;
public HttpFilterConfigurer(final JwtTokenUtil jwtTokenUtil, final ObjectMapper objectMapper) {
AssertUtils.notNull(jwtTokenUtil, "jwtTokenUtil");
AssertUtils.notNull(objectMapper, "objectMapper");
this.jwtTokenUtil = jwtTokenUtil;
this.objectMapper = objectMapper;
}
#Override
public void configure(HttpSecurity http) throws Exception {
final CopyRequestBodyHttpFilter copyRequestBodyFilter = new CopyRequestBodyHttpFilter();
http.addFilterBefore(copyRequestBodyFilter, UsernamePasswordAuthenticationFilter.class);
final CorrelationIdLoggingHttpFilter correlationIdLoggingFilter = new CorrelationIdLoggingHttpFilter();
http.addFilterAfter(correlationIdLoggingFilter, CopyRequestBodyHttpFilter.class);
final JwtTokenHttpFilter customFilter = new JwtTokenHttpFilter(jwtTokenUtil, objectMapper);
http.addFilterAfter(customFilter, CorrelationIdLoggingHttpFilter.class);
final WhitelistOriginHttpFilter whitelistOriginFilter = new WhitelistOriginHttpFilter(objectMapper);
http.addFilterAfter(whitelistOriginFilter, JwtTokenHttpFilter.class);
}
}

Why my custom PermissionEvaluator isn't invoked?

I'm struggling with my Spring Security configuration which I wasn't able to make it works so far.
I don't know why my custom PermissionEvaluator is not getting invoked and my #PreAuthorize annotation using hasPermission expression are ignored.
I'm using Spring 4.2.4 and Spring security 4.1.0
Her is my code :
Web Security configuration
#Configuration
#EnableWebSecurity
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http //
.addFilterBefore(wafflePreAuthFilter(), AbstractPreAuthenticatedProcessingFilter.class) //
.authenticationProvider(preauthAuthProvider()) //
.csrf().disable() //
.authorizeRequests() //
.antMatchers("/ui/**").authenticated() //
.anyRequest().permitAll();
}
#Bean
public WafflePreAuthFilter wafflePreAuthFilter() throws Exception {
WafflePreAuthFilter filter = new WafflePreAuthFilter();
filter.setAuthenticationManager(authenticationManager());
return filter;
}
#Bean
public PreAuthenticatedAuthenticationProvider preauthAuthProvider() {
PreAuthenticatedAuthenticationProvider preauthAuthProvider = new PreAuthenticatedAuthenticationProvider();
preauthAuthProvider.setPreAuthenticatedUserDetailsService(userDetailsServiceWrapper());
return preauthAuthProvider;
}
#Bean
public UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken> userDetailsServiceWrapper() {
UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken> wrapper = new UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken>();
wrapper.setUserDetailsService(myUserDetailsService());
return wrapper;
}
#Bean
public UserDetailsService myUserDetailsService() {
return new myUserDetailsService();
}
}
Method Security configuration
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, proxyTargetClass = true)
public class MyServiceMethodSecurityConfig extends GlobalMethodSecurityConfiguration {
#Bean
public PermissionEvaluator myPermissionEvaluator() {
return new DcePermissionEvaluator();
}
#Override
public MethodSecurityExpressionHandler createExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(myPermissionEvaluator());
return expressionHandler;
}
}
PermissionEvaluator
public class MyPermissionEvaluator implements PermissionEvaluator {
#Autowired
private MyService myAutowiredService;
#Override
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
// checking permissions
return true;
}
#Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
// checking permissions
return true;
}
}
Anyone can give me an hint on what to do ?
By the way if I change MyServiceMethodSecurityConfig into this, then myPermissionEvaluator is processed but dependencies injection doesn't work as it isn't managed by Spring :
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, proxyTargetClass = false)
public class MyServiceMethodSecurityConfig extends GlobalMethodSecurityConfiguration {
#Override
public MethodSecurityExpressionHandler createExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(new DcePermissionEvaluator());
return expressionHandler;
}
}
I ran into this issue. It seemed to be caused by the annotation #EnableGlobalMethodSecurity being specified in multiple places.
Once I removed it from locations other than above my implementation of GlobalMethodSecurityConfiguration things started working as expected.

Spring Security Role Hierarchy not working using Java Config

First of all, I am new to Java Spring Framework. So forgive me if I did not provide enough info. I have tried to add RoleHierarchy into my app but it did not work. Below are the codes I have tried.
SecurityConfig.java
// These config is try to set up a user Role Hierarchy
#Bean
public RoleHierarchy roleHierarchy() {
System.out.println("arrive public RoleHierarchy roleHierarchy()");
RoleHierarchyImpl r = new RoleHierarchyImpl();
r.setHierarchy("ROLE_ADMIN > ROLE_STAFF");
r.setHierarchy("ROLE_STAFF > ROLE_USER");
r.setHierarchy("ROLE_DEVELOPER > ROLE_USER");
r.setHierarchy("ROLE_USER > ROLE_GUEST");
return r;
}
#Bean
public AffirmativeBased defaultAccessDecisionManager(RoleHierarchy roleHierarchy){
System.out.println("arrive public AffirmativeBased defaultAccessDecisionManager()");
List<AccessDecisionVoter> decisionVoters = new ArrayList<>();
// webExpressionVoter
WebExpressionVoter webExpressionVoter = new WebExpressionVoter();
DefaultWebSecurityExpressionHandler
expressionHandler = new DefaultWebSecurityExpressionHandler();
expressionHandler.setRoleHierarchy(roleHierarchy);
webExpressionVoter.setExpressionHandler(expressionHandler);
decisionVoters.add(webExpressionVoter);
decisionVoters.add(roleHierarchyVoter(roleHierarchy));
// return new AffirmativeBased(Arrays.asList((AccessDecisionVoter) webExpressionVoter));
return new AffirmativeBased(decisionVoters);
}
#Bean
public RoleHierarchyVoter roleHierarchyVoter(RoleHierarchy roleHierarchy) {
System.out.println("arrive public RoleHierarchyVoter roleHierarchyVoter");
return new RoleHierarchyVoter(roleHierarchy);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// skipping some codes
http
// skipping some codes
.accessDecisionManager(defaultAccessDecisionManager(roleHierarchy()))
// skipping some codes
}
MethodSecurityConfig.java
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
#Inject
private SecurityConfig securityConfig;
#Override
protected AuthenticationManager authenticationManager() throws Exception {
return securityConfig.authenticationManagerBean();
}
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
System.out.println("arrive protected MethodSecurityExpressionHandler createExpressionHandler()");
DefaultMethodSecurityExpressionHandler d = new DefaultMethodSecurityExpressionHandler();
d.setRoleHierarchy(securityConfig.roleHierarchy());
return d;
}
}
And I have a UserDetailsServiceImpl implements UserDetailsService that provide the principal, Authentication and GrantedAuthority
Finally I have some APIs:
#PreAuthorize("hasRole('ROLE_STAFF')")
#RequestMapping(value = "/api/v1/contactUs", method = RequestMethod.GET)
#PreAuthorize("hasRole('ROLE_DEVELOPER')")
#RequestMapping(value = "/api/v1/system", method = RequestMethod.GET)
The problem is now if I login as ROLE_STAFF, ROLE_DEVELOPER, ROLE_ADMIN, I got the following result.
| API | ROLE_STAFF | ROLE_DEVELOPER | ROLE_ADMIN |
|-----------|------------|----------------|------------|
| contactUs | 200 | 403 | 403 |
| system | 403 | 200 | 403 |
As you can see ROLE_STAFF and ROLE_DEVELOPER work just fine. But I want ROLE_ADMIN as a super role of both and it didn't work.
FYI, I am using spring-security 3.2.5.RELEASE
The issue is in the RoleHierachy, which should be like this:
#Bean
public RoleHierarchy roleHierarchy() {
RoleHierarchyImpl r = new RoleHierarchyImpl();
r.setHierarchy("ROLE_ADMIN > ROLE_STAFF and ROLE_ADMIN > ROLE_DEVELOPER and ROLE_STAFF > ROLE_USER and ROLE_DEVELOPER > ROLE_USER");
return r;
}
keep calling setHierarchy() will override the setting before
Everytime I want to implement a hierarchy of roles with Spring Security and Java config, I use the following approach:
We have to add a RoleHierarchyImpl bean into context (You see, that I use multiple roles to build a hierarchy):
#Bean
public RoleHierarchyImpl roleHierarchy() {
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_DBA ROLE_DBA > ROLE_USER ");
return roleHierarchy;
}
Then we need to create web expression handler to pass obtained hierarchy to it:
private SecurityExpressionHandler<FilterInvocation> webExpressionHandler() {
DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler = new DefaultWebSecurityExpressionHandler();
defaultWebSecurityExpressionHandler.setRoleHierarchy(roleHierarchy());
return defaultWebSecurityExpressionHandler;
}
The final step is to add expressionHandler into http.authorizeRequests():
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.expressionHandler(webExpressionHandler())
.antMatchers("/admin/**").access("(hasRole('ROLE_ADMIN') or hasRole('ROLE_DBA')) and isFullyAuthenticated()")
.antMatchers("/dba").access("hasRole('ROLE_DBA') and isFullyAuthenticated()")
.antMatchers("/dba/**").access("hasRole('ROLE_USER')")
.and()
.requiresChannel()
.antMatchers("/security/**").requiresSecure()
.anyRequest().requiresInsecure()
.and()
.formLogin()
.loginPage("/login")
.failureUrl("/login?auth=fail")
.usernameParameter("username")
.passwordParameter("password")
.defaultSuccessUrl("/admin")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.deleteCookies("remember-me")
.invalidateHttpSession(true)
.logoutSuccessUrl("/index")
.permitAll()
.and()
.csrf()
.and()
.rememberMe().tokenValiditySeconds(1209600)
.and()
.exceptionHandling().accessDeniedPage("/403")
.and()
.anonymous().disable()
.addFilter(switchUserFilter());
}
Result: in this particular example we try to visit /dba section after we have logged in using admin user (ROLE_ADMIN). Before we created a hierarchy, we had an access denied result, but now we can visit this section without any problems.
Note: The accepted answer won't work in the newest version of Spring security (I think since release 5.2.1).
This is because the 'and' (ROLE_1 > ROLE_2 and ROLE_2 > ROLE_3) notation was never an official standard. You could have written every word instead of 'and' and it would still work the same in the past versions.
Instead, in the new version you should now use '\n' (new line), e.g. ROLE_1 > ROLE_2\nROLE2 > ROLE_3 ...
For me the solution was having proper bean name for the instance of DefaultWebSecurityExpressionHandler. The name should be webSecurityExpressionHandler.
#Bean
public RoleHierarchyImpl roleHierarchy() {
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
roleHierarchy.setHierarchy(Roles.getRoleHierarchy());
return roleHierarchy;
}
#Bean
public DefaultWebSecurityExpressionHandler webSecurityExpressionHandler() {
DefaultWebSecurityExpressionHandler expressionHandler = new DefaultWebSecurityExpressionHandler();
expressionHandler.setRoleHierarchy(roleHierarchy());
return expressionHandler;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.expressionHandler(webSecurityExpressionHandler())
...
}
Override the createExpressionHandler method so it returns a configured global expression handler
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class GlobalMethodSecurityConfig extends GlobalMethodSecurityConfiguration {
#Autowired
private RoleHierarchy roleHierarchy;
#Override
protected MethodSecurityExpressionHandler createExpressionHandler(){
return methodSecurityExpressionHandler();
}
private DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler(){
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setRoleHierarchy(roleHierarchy);
return expressionHandler;
}
#Bean
public RoleHierarchyImpl roleHierarchy() {
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_OWNER > ROLE_USER");
return roleHierarchy;
}
#Bean
public RoleHierarchyVoter roleVoter() {
return new RoleHierarchyVoter(roleHierarchy);
}
#Configuration
public static class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {}
}
}
Correct format is: ROLE_A > ROLE_B and ROLE_B > ROLE_C. see doc.
#Bean
public RoleHierarchy roleHierarchy() {
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
roleHierarchy.setHierarchy(UserRole.ROLE_HIERARCHY);
return roleHierarchy;
}
I don't use Spring RoleHierarchy - because it doea not work for me.
But Ussualy I do like this:
Define Role interface
public static interface Role {
String getName();
List<String> getHierarchy();
}
List of my roles (to store in DB):
public interface AuthStates {
// Spring security works fine only with ROLE_*** prefix
String ANONYMOUS = "ROLE_ANONYMOUS";
String AUTHENTICATED = "ROLE_AUTHENTICATED";
String ADMINISTRATOR = "ROLE_ADMINISTRATOR";
}
Define Anonymous role as basic role class:
public static class Anonymous implements Role {
private final String name;
private final List<String> hierarchy = Lists.newArrayList(ANONYMOUS);
public Anonymous() {
this(ANONYMOUS);
}
protected Anonymous(String name) {
this.name = name;
}
#Override
public String getName() {
return name;
}
#Override
public List<String> getHierarchy() {
return hierarchy;
}
protected void addHierarchy(String name) {
hierarchy.add(name);
}
}
Define Authenticated role (common user role):
public static class Authenticated extends Anonymous {
public Authenticated() {
this(AUTHENTICATED);
}
protected Authenticated(String name) {
super(name);
addHierarchy(AUTHENTICATED);
}
}
Define Administrator role (at the top of the evolution):
public static class Administrator extends Authenticated {
public Administrator() {
this(ADMINISTRATOR);
}
protected Administrator(String name) {
super(name);
addHierarchy(ADMINISTRATOR);
}
}
Optional - static factory class:
public static Role getRole(String authState) {
switch (authState) {
case ANONYMOUS: return new Anonymous();
case AUTHENTICATED: return new Authenticated();
case ADMINISTRATOR: return new Administrator();
default: throw new IllegalArgumentException("Wrong auth state");
}
}
In my CustomUserDetailsService (which implements UserDetailsService) I use role like this:
private Collection<GrantedAuthority> createAuthority(User user) {
final List<GrantedAuthority> authorities = new ArrayList<>();
AuthStates.Role userAuthState = AuthStates.getRole(user.getAuthState());
for (String role : userAuthState.getHierarchy()) {
authorities.add(new SimpleGrantedAuthority(role));
}
return authorities;
}
authorities
In controllers:
#PreAuthorize("hasRole('ROLE_AUTHENTICATED')")
Will allow user logged in as ROLE_AUTHENTICATED and ROLE_ADMINISTRATOR both.

How to secure a web application with multiple realms using Spring's Java Config?

I have a web application with 2 types of resources.
web pages
web services
I want to secure the web pages using one authentication provider (i.e. CAS) and the web services using another authentication provider (i.e. BASIC authentication).
I found a solution which could work here, but it uses XML, and I would prefer to not use XML configuration if possible.
Is there a Java Config solution to this?
Well it took a while to figure out how to do it...
Basically I split up my original security configuration class into 3 separate configuration classes.
This is basically how I did it...
The main security configuration...
#Configuration
#Import({WebPageSecurityConfig.class, WebServiceSecurityConfig.class})
public class SecurityConfig {
}
The security configuration for web pages... (URL does not begin with /service/**)
#Configuration
#Order(200)
#EnableWebMvcSecurity
public class WebPageSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(final AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(casAuthenticationProvider());
}
#Override
public void configure(final HttpSecurity http) throws Exception {
http.csrf().disable();
http.requestMatcher(new RequestMatcher() {
#Override
public boolean matches(final HttpServletRequest request) {
final String url = request.getServletPath() + StringUtils.defaultString(request.getPathInfo());
return !(url.startsWith("/service/"));
}
});
http.addFilter(casAuthenticationFilter()).exceptionHandling().authenticationEntryPoint(casAuthenticationEntryPoint());
http.authorizeRequests().
antMatchers("/securedPage").hasAuthority("ROLE_CAS_USER"). // /securedPage can only be accessed by cas user
anyRequest().permitAll(); // all other pages are unsecured
}
// General Application Security (CAS Authentication)
#Bean
public CasAuthenticationFilter casAuthenticationFilter() throws Exception {
final CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter();
casAuthenticationFilter.setAuthenticationManager(authenticationManager());
return casAuthenticationFilter;
}
#Bean
public CasAuthenticationEntryPoint casAuthenticationEntryPoint() {
final CasAuthenticationEntryPoint casAuthenticationEntryPoint = new CasAuthenticationEntryPoint();
casAuthenticationEntryPoint.setLoginUrl(env.getRequiredProperty("cas.server.url") + "/login");
casAuthenticationEntryPoint.setServiceProperties(casServiceProperties());
return casAuthenticationEntryPoint;
}
#Bean
public ServiceProperties casServiceProperties() {
final ServiceProperties serviceProperties = new ServiceProperties();
serviceProperties.setService(env.getRequiredProperty("cas.service.url") + "/j_spring_cas_security_check");
serviceProperties.setSendRenew(false);
return serviceProperties;
}
#Bean
public CasAuthenticationProvider casAuthenticationProvider() {
final CasAuthenticationProvider casAuthenticationProvider = new CasAuthenticationProvider();
casAuthenticationProvider.setAuthenticationUserDetailsService(casAuthenticationUserDetailsService());
casAuthenticationProvider.setServiceProperties(casServiceProperties());
casAuthenticationProvider.setTicketValidator(casTicketValidator());
casAuthenticationProvider.setKey("casAuthenticationProviderKey");
casAuthenticationProvider.setStatelessTicketCache(casStatelessTicketCache());
return casAuthenticationProvider;
}
#Bean
public AuthenticationUserDetailsService casAuthenticationUserDetailsService() {
final AbstractCasAssertionUserDetailsService authenticationUserDetailsService = new AbstractCasAssertionUserDetailsService() {
#Override
protected UserDetails loadUserDetails(final Assertion assertion) {
final String username = assertion.getPrincipal().getName();
final List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_CAS_USER"));
return new User(username, "notused", authorities);
}
};
return authenticationUserDetailsService;
}
#Bean
public TicketValidator casTicketValidator() {
final Saml11TicketValidator ticketValidator = new Saml11TicketValidator(env.getRequiredProperty("cas.server.url"));
ticketValidator.setTolerance(env.getRequiredProperty("cas.ticket.tolerance", Long.class));
return ticketValidator;
}
#Bean
public StatelessTicketCache casStatelessTicketCache() {
final EhCacheBasedTicketCache ticketCache = new EhCacheBasedTicketCache();
ticketCache.setCache(casCache());
return ticketCache;
}
#Bean(initMethod = "initialise", destroyMethod = "dispose")
public Cache casCache() {
final Cache cache = new Cache("casTickets", 50, true, false, 3600, 900);
return cache;
}
#Autowired
private Environment env;
}
The security configuration for RESTful web services (URL starts with /service/**)
#Configuration
#Order(300)
#EnableWebMvcSecurity
public class WebServiceSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(final AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().
withUser("admin").password("password").authorities(new SimpleGrantedAuthority("ROLE_WS_USER"));
}
#Override
public void configure(final HttpSecurity http) throws Exception {
http.csrf().disable();
http.
antMatcher("/service/**"). // only process URLs that begin with /service/
sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and(). // RESTful web services are stateless
addFilter(wsAuthenticationFilter()).exceptionHandling().authenticationEntryPoint(wsAuthenticationEntryPoint());
http.authorizeRequests().anyRequest().hasAuthority("ROLE_WS_USER"); // all requests are secured
}
// Web Service Security (BASIC Authentication)
#Bean
public BasicAuthenticationFilter wsAuthenticationFilter() throws Exception {
final BasicAuthenticationFilter wsAuthenticationFilter = new BasicAuthenticationFilter(authenticationManager(), wsAuthenticationEntryPoint());
return wsAuthenticationFilter;
}
#Bean
public BasicAuthenticationEntryPoint wsAuthenticationEntryPoint() {
final BasicAuthenticationEntryPoint wsAuthenticationEntryPoint = new BasicAuthenticationEntryPoint();
wsAuthenticationEntryPoint.setRealmName("My Realm");
return wsAuthenticationEntryPoint;
}
#Autowired
private Environment env;
}
It's explain how to create multiple securities in the docs
http://docs.spring.io/spring-security/site/docs/current/reference/html/jc.html#multiple-httpsecurity
something like this should work
#EnableWebSecurity
public class MultiHttpSecurityConfig {
#Configuration
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Resource private UserDetailsService userBasicAuthService;
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/**")
.userDetailsService(userBasicAuthService)
.authorizeRequests()
.and()
.httpBasic();
}
}
#Configuration
public static class PagesWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Resource private UserDetailsService userCasService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/pages/**")
.userDetailsService(userCasService)
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin();
}
}
}

Spring social and security integration (vs anonymouse user)

I tried to integrate spring security and spring social. The problem is when I try to create account for him in my system. I would like to avoid creating user with id "anonymousUser".
For expected behaviour when I analyzed code from spring-social-security I expected to call method JdbcUsersConnectionRepository.findUserIdsWithConnection(Connection connection) for creating user and entry in the system. But when I run my code I find out this is not true. Before this action is calling implementation of SocialConfigurer.getUserId(). Great... If we have anonymous user it will be cached... But anyway getUserId is called before anything so authorization doesn't work. So I wrote my own implementation to this thing:
#Configuration
#EnableSocial
public class SocialConfig implements SocialConfigurer {
#Autowired
private DataSource dataSource;
#Autowired
private CustomerService customerService;
#Autowired
private CustomerProviderRepository customerProviderRepository;
#Autowired
private PasswordEncoder passwordEncoder;
#Override
public void addConnectionFactories(ConnectionFactoryConfigurer connectionFactoryConfigurer, Environment environment) {
}
#Override
public UserIdSource getUserIdSource() {
return () -> {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || authentication.getName().equals("anonymousUser")) {
throw new IllegalStateException("Unable to get a ConnectionRepository: no user signed in");
}
return authentication.getName();
};
}
#Override
public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
JdbcUsersConnectionRepository repository = new JdbcUsersConnectionRepository(dataSource, connectionFactoryLocator, Encryptors.noOpText());
repository.setConnectionSignUp(new SocialConnectionSignUpService(customerService, customerProviderRepository, passwordEncoder));
return repository;
}
}
Implementation of SocialCOnnectionSignUpService
public class SocialConnectionSignUpService implements ConnectionSignUp {
private final CustomerService customerService;
private final CustomerProviderRepository customerProviderRepository;
private final PasswordEncoder passwordEncoder;
public SocialConnectionSignUpService(CustomerService customerService, CustomerProviderRepository customerProviderRepository, PasswordEncoder passwordEncoder) {
this.customerService = customerService;
this.passwordEncoder = passwordEncoder;
this.customerProviderRepository = customerProviderRepository;
}
#Override
public String execute(Connection<?> connection) {
ConnectionKey connectionKey = connection.getKey();
UserProfile profile = connection.fetchUserProfile();
Customer customer = customerService.findBy(profile.getEmail());
if(customer == null) {
customer = new Customer();
customer.setEmail(profile.getEmail());
customerService.add(customer);
}
SocialKey key = new SocialKey();
key.setUserId(customer.getId().toString());
key.setProviderUserId(connectionKey.getProviderUserId());
key.setProviderId(connectionKey.getProviderId());
CustomerProvider customerProvider = new CustomerProvider();
customerProvider.setKey(key);
customerProvider.setDisplayName(profile.getName());
customerProviderRepository.save(customerProvider);
return customerProvider.id();
}
}
And configuration of spring security:
#Configuration
#EnableWebSecurity
public class SecurityContext extends WebSecurityConfigurerAdapter {
#Autowired
private DataSource dataSource;
#Autowired
private CustomerRepository customerRepository;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/login/authenticate")
.permitAll()
.and()
.rememberMe()
.and()
.authorizeRequests()
.antMatchers("/signup/social").authenticated()
.antMatchers("/**").permitAll()
.and()
.apply(new SpringSocialConfigurer().postLoginUrl("/signup/social").alwaysUsePostLoginUrl(true))
.and()
.csrf().disable();
}
#Bean
public SocialUserDetailsService socialUserDetailsService() {
return new SocialUserDetailsServiceImpl(customerRepository);
}
}
Do you have any workaround or solution how to pass the problem?

Categories

Resources