Spring Security 3: Method Level Access Failure - spring-security

I have url-level security in placed, and also, method level. But my method level security is bypassed once the user has been authenticated at the url-level! I looked at this further and it seems that the following url-level security:
intercept-url pattern="/**" access="ROLE_USER"
would override any of my method level security (like below code snippet).
#PreAuthorize("hasRole('ROLE_SUPERVISOR')")
public String supervisorRoleOnly()
{
return "success!!!" ;
}
I would think that this method would throw an access-denied error, but no, any ROLE_USER can access this method once they are already authenticated at the url-level.
I do have this in my security-config.xml:
<global-method-security pre-post-annotations="enabled" >
<expression-handler ref="expressionHandler"/>
</global-method-security>
What am I missing?

I guess this applies more to future readers, but when you set debug logging for Spring Security do you see something similar to following:
Looking for Pre/Post annotations for method 'supervisorRoleOnly' on target class 'yourClassName'
No expression annotations found
Adding security method [CacheKey[yourClassName; public yourReturnType yourClassName.supervisorRoleOnly()]] with attributes [ROLE_USER]
PreAuthorize is probably being ignored.

Related

Spring Security : How to Authorize functions without log in

I implemented spring security in my web application. Now all my services are secured and can be only invoked by authorised users. Everything works on webside, but when I call function without log in doesn't work.
here is my Controller(RestController)
#RestController
public class MessageService {
#Autowired
MessageModel messageModel;
#RequestMapping(value="/message",method=RequestMethod.POST)
public Message save(#RequestBody Message message) {
return messageModel.save(message);
}
#PreAuthorize("permitAll()")
#RequestMapping(value="/messagee",method=RequestMethod.POST)
public Message savee(#RequestBody Message message) {
System.out.println("hjgjhghggfhgf");
return messageModel.savee(message);
}
}
I am using angularjs client side.
The functions are not supposed to work as they are secured by spring security. To allow a few functions to be accessed anonymously, you need to configure spring security to do so.
Depending on what configuration you are using, I would suggest the below:
If you are using XML/Java route based config, I would recommend you to do something like this:
<security:intercept-url pattern="/trusted/**" filters="none" />
<security:intercept-url pattern="/**" access="isFullyAuthenticated()" />
If you are using the #PreAuthorize annotation, I would recommend you to do something like this:
#PreAuthorize("permitAll()")
public void YourAnonymousController(){
}
Hope this is what you are looking for.
EDIT 1: Please note that you remove the route based security config. Try adding #PreAuthorize("hasRole()") on functions you want to keep secured and #PreAuthorize("permitAll()") on anonymous functions.

Spring security - complex logic deciding whether to require login, or allow anonymous access

Using spring securiity, I'm faced with a requirement for a complex business decision about whether to allow anonymous access or insist on user login. Logic is too complex for "intercept-url" expressions ( intercept-url pattern='/mypattern/**'...)
E.g. assume a RESTFul books service:
https://myserver/rest/book?title=war_and_peace
https://myserver/rest/book?title=the_firm
Say "war and peace" allows anonymous access because its old and its copyrights have expired.
While "the firm" is copyrighted & requires login (so as to charge the user).
This copyright info is available in a database.
Could anyone please offer any hints as to how to achieve this in spring secuirity? thanks in advance
There are of course multiple ways that code can look like, but to get the idea:
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth instanceof AnonymousAuthenticationToken && "the_firm".equals(title)) {
throw new AccessDeniedException("Requires login!");
}
Update: A more declarative way is to use annotations:
#PostAuthorize("hasPermission(returnObject, 'READ')")
public Book findBook(String title) {
// ...
}
This requires that you create a permission evaluator bean and add config:
<global-method-security pre-post-annotations="enabled">
<expression-handler ref="expressionHandler" />
</global-method-security>
#Bean
public DefaultMethodSecurityExpressionHandler expressionHandler() {
DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
handler.setPermissionEvaluator(permissionEvaluator);
return handler;
}

Spring Security - check if web url is secure / protected

Is there a way to "ask" spring security if the current request is secure? Because even if I am authenticated I want to detect if I am in a secure protected URL or in a anonymous / public page
Thanks in advance!
Spring Security provides JSP tag support for this. For example:
<sec:authorize url="/admin">
This content will only be visible to users who are authorized to access the "/admin" URL.
</sec:authorize>
Thymeleaf provides a Spring Security Dialect that has direct support for checking URL authorization with Spring Security. For example:
<div sec:authorize-url="/admin">
This will only be displayed if authenticated user can call the "/admin" URL.
</div>
If your technology does not support performing the check directly, you can easily use the WebInvocationPrivilegeEvaluator (this is the object that the JSP taglib and Thymeleaf use). For example, you can #Autowire an instance of WebInvocationPrivilegeEvaluator and use it directly. Obviously the syntax will vary depending on where you use it (i.e. GSP, Freemarker, etc), but here is an example in straight Java code.
#Autowired
WebInvocationPrivilegeEvaluator webPrivs;
public void useIt() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
boolean hasAdminAccess = webPrivs.isAllowed("/admin", authentication);
boolean hasAdminPostAccess = webPrivs.isAllowed(null, "/admin", "POST", authentication);
}
I think you can use your own implementation for AccessDecisionVoter then just simply override Vote method and compare intercept url using filterInvocation.getRequestUrl(); method.
#Override
public int vote(Authentication authentication, FilterInvocation filterInvocation,
Collection<ConfigAttribute> attributes) {
String requestUrl = filterInvocation.getRequestUrl();
....
}
We can mark it is as secure channel so converted to https:// url.
<intercept-url pattern="/**" access="IS_AUTHENTICATED_FULLY" requires-channel="https" />
We can then use request.getScheme() to identify it.
I used org.springframework.security.version 3.1.4.RELEASE

Spring Security Authentication using MyBatis

I am trying to perform Spring Security Authentication using MyBatis.
My spring-security file looks as follows
<global-method-security pre-post-annotations="enabled" />
<beans:bean id="myUserService"
class="com.service.CustomService" />
<authentication-manager>
<authentication-provider user-service-ref="myUserService" />
</authentication-manager>
My CustomService class implements UserDetailsService and in the loadUserByUsername method , I am using my MyBatis Dao to load the Users from the DB.
#Autowired
private MyBatisDao dao;
In my Controller class I am using the same annotation , and in that case it returns the proper object.
But when I use the same in the CustomService class it returns null.
I am unable to understand the reason for it. Am i missing something. Please help
Any example of Spring Authentication using MyBatis would help, I can understand it and then maybe figure out the issue
I fix this issue by another - not recommended way.
In this case, #controller can treat the db work right,
so I do basic auth in controller and send that result to custom service.
Custom service has no valid auth function in this case.
If useename and password is valid, custom service is called,
and that just returns dummy auth result.
If username and password is invalid, i just didn't call the auth in controller.
Though it's not quite right way, it works fine with some special treat for user role.
I want to know there's a better way to solve this problem,
but i have no time to find, right now.
Issue solved by taking an alternative approach in constructing the MyBatis Object.
I created a singleton class which returns the SqlSessionFactory Object, and using the same in my code for calling the Mapper Interfaces methods.
Sample code snippet below
InputStream myBatisConfigStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("config.xml");
if (null == sqlSessionFactory){
sqlSessionFactory = new SqlSessionFactoryBuilder().build(myBatisConfigStream);
sqlSessionFactory.getConfiguration().addMapper(IOMapper.class);
}
try {
myBatisConfigStream.close();
} catch (IOException e) {
e.printStackTrace();
}
public static SqlSessionFactory getSqlSessionFactory() {
return sqlSessionFactory;
}
The config.xml file contains only the dataSource.
Hope this helps.

Problem with Remember Me Service in Spring Security

I'm trying to implement a "remember me" functionality in my website using Spring. The cookie and entry in the persistent_logins table are getting created correctly. Additionally, I can see that the correct user is being restored as the username is displayed at the top of the page.
However, once I try to access any information for this user when they return after they were "remembered", I get a NullPointerException. It looks as though the user isn't being set in the session again.
My applicationContext-security.xml contains the following:
<remember-me data-source-ref="dataSource" user-service-ref="userService"/>
...
<authentication-provider user-service-ref="userService" />
<jdbc-user-service id="userService" data-source-ref="dataSource"
role-prefix="ROLE_"
users-by-username-query="select email as username, password, 1 as ENABLED from user where email=?"
authorities-by-username-query="select user.id as id, upper(role.name) as authority from user, role, users_roles where users_roles.user_fk=id and users_roles.role_fk=role.name and user.email=?"/>
I thought it may have had something to do with users-by-username query but surely login wouldn't work correctly if this query was incorrect?
Any help on this would be greatly appreciated.
Thanks,
gearoid.
Can you please include the entire stack trace of the exception? I suspect that because you have not set the key attribute on the remember-me configuration that you specified above that the token is not being set on the SecurityContextHolder.
To see details of how Remember Me works you should take a look at the source for the RememberMeAuthenticationFilter. You can find that source here (directly):
http://grepcode.com/file/repo1.maven.org/maven2/org.springframework.security/spring-security-web/3.0.2.RELEASE/org/springframework/security/web/authentication/rememberme/RememberMeAuthenticationFilter.java
RememberMeAuthenticationFilter is going to call in the RememberMeAuthenticationProvider as a result of:
rememberMeAuth = authenticationManager.authenticate(rememberMeAuth);
Inside the authenticate method you can see that it will throw an exception if you do not specify a key:
if (this.key.hashCode() != ((RememberMeAuthenticationToken) authentication).getKeyHash()) {
throw new BadCredentialsException(messages.getMessage("RememberMeAuthenticationProvider.incorrectKey",
"The presented RememberMeAuthenticationToken does not contain the expected key"));
}
The key can literally be any string "your-company-name-{GUID}" or something like that. So then your remember-me would look more like this:
<remember-me key="your-company-name-rmkey-aWeFFTgxcv9u1XlkswUUiPolizxcwsqUmml" token-validity-seconds="3600" data-source-ref="dataSource"/>
Setting the token-validity is a really good idea which you should do.
Grant

Resources