In my web app users are able to change their user details. The URL for this page is:
springproject/usermanagement/edituserinfo/4
where "4" is the user id.
My security-context looks like:
<security:http auto-config="true" use-expressions="true">
<security:intercept-url pattern="/usermanagement" access="isAuthenticated()" />
<security:intercept-url pattern="/usermanagement/new" access="hasRole('ROLE_ADMIN')" />
<security:intercept-url pattern="/usermanagement/edit/*" access="hasRole('ROLE_ADMIN')" />
<security:intercept-url pattern="/usermanagement/edituserinfo/*" access="isAuthenticated()" />
</security:http>
How can I restrict the user only to access their own "edituserinfo" page? E.g. user with user id 1 can only access: "springproject/usermanagement/edituserinfo/1 " and not "springproject/usermanagement/edituserinfo/4 "
You can also accomplish this using Spring Security's #PreAuthorize which supports expressions:
#PreAuthorize("#userId == principal.id")
public void doSomething(#PathVariable String userId);
See the Spring docs:
Access Control using #PreAuthorize and #PostAuthorize
Use a PathVariable on the URL, like #RequestMapping("/usermanagement/edituserinfo/{userid}") and in your code validate the logged-in user's Spring Security context principle (via SecurityContextHolder.getContext().getAuthentication().getPrincipal()) against the userid path variable. If they don't match, bounce the user out, log a SecurityException, and send an email to the admins.
Related
Greeting everyone, I try to configure simple authorization code flow via Spring Security OAuth.
I tested my authorisation and resource server configuration via following approaches:
Create a web application as client and use its page to fire http post call to /oauth/authorize.
After getting code, I use the same page to
fire another http post with code and get token.
At the end, I use
curl -H to place token inside header and get response from protected
resource.
But when I try to use rest template. It throw error message 401 Unauthorised error.
Server side - security configure:
<http auto-config="true" pattern="/protected/**"
authentication-manager-ref="authenticationManager">
<custom-filter ref="resourceFilter" before="PRE_AUTH_FILTER" />
<csrf disabled="true" />
</http>
<http auto-config="true">
<intercept-url pattern="/**" access="hasRole('ROLE_USER')" />
<form-login default-target-url="/admin.html" />
<logout logout-success-url="/welcome.html" logout-url="/logout"/>
<csrf disabled="true" />
</http>
<authentication-manager alias="authenticationManager">
<authentication-provider>
<user-service>
<user name="admin" password="123456" authorities="ROLE_USER,ROLE_ADMIN" />
</user-service>
</authentication-provider>
</authentication-manager>
Server side - authorisation and resource configure:
<oauth:authorization-server
client-details-service-ref="clientDetails" error-page="error">
<oauth:authorization-code />
</oauth:authorization-server>
<oauth:client-details-service id="clientDetails">
<oauth:client client-id="admin" secret="fooSecret" />
</oauth:client-details-service>
<oauth:resource-server id="resourceFilter" />
Client Side:
<oauth:client id="oauth2ClientContextFilter" />
<oauth:resource id="sso" client-id="admin"
access-token-uri="http://localhost:8080/tough/oauth/token"
user-authorization-uri="http://localhost:8080/tough/oauth/authorize"
use-current-uri="true" client-secret="secret"
client-authentication-scheme="header" type="authorization_code"
scope="trust" />
<oauth:rest-template id="template" resource="sso"/>
If anyone knows where goes wrong, please do let me know.
There were two issues with my configuration above.
I noticed my client used wrong secret to communicate with authorization server.
Token endpoint at authorization server use authentication manager which
serve user authentication. It result
client are rejected all times until I create new security realm for
token endpoint and configure it to use a authentication manger designed for
client.
Note client is different from user. Client is third party want to access resource belong to your user (also called resource owner).
I had the same problem. It helped to add a
org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService
to spring securities authentication-manager, glueing the clientDetailsService to the authentication manager. So
<authentication-manager alias="authenticationManager">
...
<authentication-provider user-service-ref="clientDetailsUserDetailsService"/>
...
</authentication-manager>
nearly solved the problem for me. I had one more Issue: Since ClientDetailsUserDetailsService has no default constructor, spring threw Exceptions of the form
org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class
[class org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService]:
Common causes of this problem include using a final class or a non-visible class;
nested exception is java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given
Which I could not solve without using a copy of that class receiving the clientDetailsService as property instead of a constructor arg.
I'm fresh off the boat to Spring Security so excuse me if this seems awfully trivial..
I try to put the Spring Security mechanism in my MVC project, but for some reason the access-denied-handler doesn't send my unauthorized user to the denied access page and instead chooses to present the login page.
Here is my Http tag in the security-context.xml:
<http authentication-manager-ref="dao-auth"
access-decision-manager-ref="accessDecisionManager"
disable-url-rewriting="true">
<intercept-url pattern="/pages/home.html" access="USER"></intercept-url>
<intercept-url pattern="/home" method="GET" access="USER"></intercept-url>
<intercept-url pattern="/logout" access="USER"></intercept-url>
<intercept-url pattern="/denied" access="ROLE_ANONYMOUS"></intercept-url>
<intercept-url pattern="/error" access="ROLE_ANONYMOUS,USER"></intercept-url>
<intercept-url pattern="/" access="ROLE_ANONYMOUS,USER"></intercept-url>
<intercept-url pattern="/pages/**" access="ROLE_ANONYMOUS,USER"></intercept-url>
<intercept-url pattern="/resources/**" access="ROLE_ANONYMOUS,USER"></intercept-url>
<form-login login-page="/login" authentication-failure-url="/denied"
default-target-url="/home" />
<logout invalidate-session="true" logout-success-url="/"
logout-url="/logout" />
<access-denied-handler error-page="/denied" />
<session-management invalid-session-url="/login">
<concurrency-control max-sessions="1"
expired-url="/login" />
</session-management>
</http>
Basically the way I test it is I try to access the /home path from the ROLE_ANONYMOUS user and instead get thrown to the /login one.
Also, can't figure out how to debug this thing or where I find the logs (feels like there are somewhere out there..)
Thanks to all responders :)
1.Spring security looks for Authentication object in security context first. If there is no authentication object (basically a principal) found in the security context, it will direct you to the login page.
2.If it finds Authentication object, then it will use the principal's authorities to do authorization.
3.When Login screen is presented, the user entered credentials are authenticated and if not authenticated, then you can throw a bad credentials exception to show the access denied error.
In my project I have problem with redirect. After successful login (from all pages), I have redirect to previous page, but for one login page I should do redirect to main page(if i don't do this, I have redirect from login page back to login page).
This my spring-security.xml
<http auto-config="true" use-expressions="true">
<form-login login-page="/spring/j_spring_security_check"
authentication-failure-url="/loginFail"
authentication-success-handler-ref="authenticationSuccessHandler"/>
<logout logout-url="/j_spring_security_logout" />
<remember-me key="remKey" user-service-ref="UserDetailsServiceImpl"
token-validity-seconds="2419200" />
</http>
<!-- After login, return to the last visited page -->
<beans:bean id="authenticationSuccessHandler"
class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
<beans:property name="useReferer" value="true" />
</beans:bean>
EDIT:
I know that is a bad solution if I add this code in my login.jsp
<security:authorize access="isAuthenticated()">
<c:redirect url="/"/>
</security:authorize>
When user is authenticated, page auto redirects to my main page
Utilise the AuthenticationSuccessHandler for manage where your landing is.
Please refer to these q & a for examples and further information:
AuthenticationSuccessHandler example for Spring Security 3
http://www.baeldung.com/spring_redirect_after_login
http://docs.spring.io/spring-security/site/docs/3.2.3.RELEASE/reference/htmlsingle/#form-login-flow-handling
Your other option is to capture redirects on that specific controller and further redirect it to the page you want:
e.g. HelloWorld -> Login -> HelloWorld Redirect -> HomePage
I've just gone through implementing a SessionCounterListener in my spring mvc webapp as per http://www.mkyong.com/servlet/a-simple-httpsessionlistener-example-active-sessions-counter/ .
I am seeing some behavior that I did not expect and have two questions.
Question 1.
When I hit my login.jsp for the first time after server restart, the session counter by 1 even before I login (not expected).
For example when I go to the login page...
sessionCreated - add one session into counter:1
And then when I go to hit the log out button the session count is decreased by one (which is fine) but immediately after the session count is increased by 1 (not expected).
For example when I hit the logout button...
sessionDestroyed - deduct one session from counter:0
sessionCreated - add one session into counter:1
It is as if everytime I go to the login page the count is increased by 1. I would expect that the count is only increased after successful login.
Can someone help me understand what is happening here?
Question 2.
When I log in as a different user in my application whilst the first user is still logged in I don't get a new session counter. i.e. I don't think it is not creating a new sessions for the new user.
Again I need help to understand please.
Here is my spring security settings....
<http pattern="/login.htm" security="none"/>
<http use-expressions="true" auto-config="false" entry-point-ref="loginUrlAuthenticationEntryPoint">
<!-- custom filters -->
<custom-filter position="FORM_LOGIN_FILTER" ref="twoFactorAuthenticationFilter" />
<custom-filter ref="securityLoggingFilter" after="SECURITY_CONTEXT_FILTER"/>
<!-- session management -->
<session-management
invalid-session-url="/sessionExpired.htm"
session-authentication-error-url="/alreadyLoggedIn.htm">
<concurrency-control
max-sessions="1"
expired-url="/sessionExpiredDuplicateLogin.htm"
error-if-maximum-exceeded="false" />
</session-management>
<!-- error handlers -->
<access-denied-handler error-page="/accessDenied.htm"/>
<!-- logout -->
<logout logout-success-url="/logout.htm" invalidate-session="true" delete-cookies="JSESSIONID" />
<!-- authorize pages -->
<intercept-url pattern="/home.htm" access="isAuthenticated()" />
<intercept-url pattern="/shortsAndOvers.htm" access="isAuthenticated()" />
<intercept-url pattern="/shortsAndOversDaily.htm" access="isAuthenticated()" />
<intercept-url pattern="/birtpage.htm" access="isAuthenticated()" />
<intercept-url pattern="/reports/show.htm" access="isAuthenticated()" />
</http>
<beans:bean id="loginUrlAuthenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<beans:property name="loginFormUrl" value="/login.htm" />
</beans:bean>
<beans:bean id="successHandler" class="com.me.reporting.security.CustomSavedRequestAwareAuthenticationSuccessHandler">
<beans:property name="defaultTargetUrl" value="/home.htm" />
</beans:bean>
<beans:bean id="failureHandler" class="com.me.reporting.security.CustomSimpleUrlAuthenticationFailureHandler">
<beans:property name="defaultFailureUrl" value="/loginfailed.htm" />
</beans:bean>
you probably are thinking about session in different context.
take a look at JSESSION cookie in firebug while clicking around your application, maybe it will give you some answers :)
HttpSessionListener probably is invoked each time JSESSIONID changes, so:
You get to login.jsp page - session is created, JSESSIONID changes, sessionCreated is invoked. You are loggin out: sessionDestroyed is invoked, session is invalidated ( not sure here, just guessing ), counter is decreased. But what happens after login out? You are probably redirected to /logout.htm and new session is created.
I'm new to Spring (and to web development in general), but I've managed to create a simple web application and now I'm trying to add authentication support to it so that users are required to sign in to access its features.
I'm using Spring MVC 3.2.2, Spring Security 3.1.4, and Tomcat 7.
For testing purposes, I was able to add authentication support using hardcoded users and everything worked correctly. Now, I must use an existing PostgreSQL database to authenticate users. I would like to emphasize that my application does not support the creation of users; users are already stored in the database.
The following is the problem:
(1) The application must authenticate users against a PostgreSQL database, which I cannot modify in any way.
(2) Passwords in the database are hashed using crypt('plain text password', gen_salt(md5)).
(3) Since I've made heavy use of annotations and xml configuration, basically most of the hard work is done by Spring, which means that a lot of things are going on behind the scenes, which I'm not aware of. As a result, I'm now stuck setting up the salt the password encoder must use to authenticate the users. Such a salt has to be the already hashed password stored in the database.
The question is:
How do I tell Spring Security to use the hashed password stored in the database as the salt of the password encoder? Is there any way to do this from xml or do I need to go ahead and implement certain class(es)?
I've searched extensively on-line and all of the examples I've found so far deviate considerably from what I have done, mainly because in the examples most of the functionally is implemented by the developer instead of mostly relying on built-in features.
This is the security.xml file:
<http use-expressions="true">
<intercept-url pattern="/resources/images/**" access="permitAll" requires-channel="any"/>
<intercept-url pattern="/resources/css/**" access="permitAll" requires-channel="any"/>
<intercept-url pattern="/login*" access="permitAll" requires-channel="any"/>
<intercept-url pattern="/**" access="hasAnyRole('ROLE_Processer', 'ROLE_Verifier', 'ROLE_Approver', 'ROLE_Supervisor', 'ROLE_Admin')" requires-channel="any"/>
<session-management>
<concurrency-control max-sessions="1" expired-url=/login?expired=true" session-registry-alias="sessionRegistry"/>
</session-management>
<form-login
login-page="/login"
default-target-url="/"
always-use-default-target="true"
authentication-failure-url="/login?error=true"/>
<logout logout-success-url="/login" invalidate-session="true" delete-cookies="JSESSIONID"/>
</http>
<authentication-manager>
<authentication-provider>
<jdbc-user-service
data-source-ref="dataSource"
users-by-username-query="SELECT id AS username, password, enabled FROM users WHERE id=?;"
authorities-by-username-query="SELECT id AS username, role FROM users WHERE id=?;"
role-prefix="ROLE_"/>
<password-encoder hash="md5">
<salt-source ???/>
</password-encoder>
</authentication-provider>
</authentication-manager>
This is the relevant excerpt from the web.xml file:
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>
org.springframework.security.web.session.HttpSessionEventPublisher
</listener-class>
</listener>
This is the relevant excerpt from the application-context.xml file:
<beans:bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
<beans:property name="driverClass" value="org.postgresql.Driver"/>
<beans:property name="url" value="jdbc:postgresql://host:port/database"/>
<beans:property name="username" value="username"/>
<beans:property name="password" value="password"/>
</beans:bean>
In terms of implementation, this is the only relevant controller LoginController.java (all others handle the actual functionality provided by the app):
#Controller
public class LoginController {
private static final Logger LOGGER = LoggerFactory.getLogger(LoginController.class);
/**
* Displays the Login form.
*/
#RequestMapping(value="/login", method=RequestMethod.GET)
public String displayLoginForm(#RequestParam(value="error", required=false) String error, #RequestParam(value="expired", required=false) String expired, Model model) {
LOGGER.info("Displaying Login form:: displayLoginForm(Model)");
if (error != null) {
LOGGER.info("Invalid username or password.");
model.addAttribute("error", "Invalid username or password.");
}
if (expired != null) {
LOGGER.info("Session has been expired (possibly due to multiple concurrent logins being attempted as the same user)");
model.addAttribute("expired", "Session has been expired (possibly due to multiple concurrent logins being attempted as the same user).");
}
return "login";
}}
Any pointers will be greatly appreciated. I would like to thank you all in advance for your time and help.
Edit:
I tried with
<password-encoder hash="md5">
<salt-source user-property="password"/>
</password-encoder>
since, from what I understood by reading the documentation, this is the user's password stored in the database. However, it doesn't work. Every time I try to log in, I get an invalid credentials message; however, the credentials are indeed valid.
Perhaps any of the events described in the Hashing and Authentication section of this page might be the reason why it is not working. I'm wondering how to write the appropriate test as suggested.
Another Edit:
As a test, I commented out the password-encoder element and tried the authentication with a plain text password (i.e., I created a test user and stored a plain text password in the database). It worked. So, the problem is definitely with the way Spring Security is encoding the password entered by the user, which does not match the hashed password stored in the database.
Well, to solve my problem I implemented a custom password encoder. Basically, I overrode the matches method of the PasswordEncoder interface:
#Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
//encoding raw password: the given encodedPassword serves as the salt
String sql = "SELECT crypt(?, ?) as hashedpwd;";
String hashedPassword = aJdbcTemplate.query(sql, new String[] {rawPassword.toString(), encodedPassword}, new ResultSetExtractor<String>() {
#Override
public String extractData(ResultSet rs) throws SQLException, DataAccessException {
rs.next();
return rs.getString("hashedpwd");
}
});
return encodedPassword.equals(hashedPassword.toString());
}