I have a website section (everything under /secure URL) that I'm trying to secure with Spring Security 3.2.5. I'm using the following XML configuration:
<http use-expressions="true">
<intercept-url pattern="/secure/login" access="permitAll" />
<intercept-url pattern="/secure/**" access="isAuthenticated()" />
<form-login default-target-url="/secure/home" always-use-default-target="true" login-page="/secure/login" />
</http>
<authentication-manager>
<authentication-provider>
<user-service>
<user name="user" password="password" authorities="ROLE_SECURE" />
</user-service>
</authentication-provider>
</authentication-manager>
I'm trying to use a custom login form for which I have this controller:
#Controller
#RequestMapping(value = "/secure")
public class LoginController {
#RequestMapping(value = "/login", method = RequestMethod.GET)
public String getLogin() {
return "secure/login";
}
#RequestMapping(value = "/home", method = RequestMethod.GET)
public String getHome() {
return "secure/home";
}
}
and this code inside the login page:
<form method="POST" action="<c:url value="/secure/login" />">
username: <input type="text" name="username" /><br/>
password: <input type="password" name="password" /><br/>
<input type="submit" value="Login" />
</form>
I have the security context loaded in the web.xml using ContextLoaderListener and the
springSecurityFilterChain delegating proxy filter is also setup.
When I try to access the /secure URL I get redirected to /secure/login, my controller is called in the getLogin method and I see my login page. That's all OK.
Now my problem: whatever I submit in the login form gets sent directly to the LoginController and I get an exception saying that POST is not a supported
method, which makes sense because there is no POST handler in the controller.
If I add a method like this in the controller:
#RequestMapping(value = "/login", method = RequestMethod.POST)
public String postLogin() {
return "redirect:/secure/home";
}
I no longer get the error but my postLogin method is invoked wich sends me to /secure/home unauthenticated which then redirects me to /secure/login and I'm back to square one.
I don't know what I'm doing wrong. All examples I see online are Java configured which I prefer not to use and all workflows hapen in the context of the application not under some extra URL path (in my case /secure).
What am I missing?
Form the docs(http://docs.spring.io/spring-security/site/docs/3.0.x/reference/appendix-namespace.html):
default-target-url:
Maps to the defaultTargetUrl property of UsernamePasswordAuthenticationFilter. If not set, the default value is "/" (the application root). A user will be taken to this URL after logging in, provided they were not asked to login while attempting to access a secured resource, when they will be taken to the originally requested URL.
You have to submit the form to j_spring_security_check
<form name='loginForm'
action="<c:url value='j_spring_security_check' />" method='POST'>
This will be handled by Spring Security and will check the user and pass depending on your config.
See this example http://www.mkyong.com/spring-security/spring-security-form-login-example/
Edit: j_security_check should also be supported.
This post helped me get the correct result: http://codehustler.org/blog/spring-security-tutorial-form-login/
Related
So I have this in my spring config xml file.
<http auto-config="true" use-expressions="true">
<intercept-url pattern="/welcome/*" access="hasRole('ADMIN')" />
<!-- <intercept-url pattern="/login" requires-channel="https" /> -->
<!-- access denied page -->
<access-denied-handler error-page="/403" />
<form-login login-page="/login"
default-target-url="/welcome"
authentication-failure-url="/login?error"
username-parameter="emailId"
password-parameter="pwd" />
<logout logout-success-url="/login?logout"/>
</http>
The role is authenticated correctly at login. I have 2 questions:
What's the difference between pattern="/welcome/*", pattern="/welcome*" and pattern="/welcome/**"? When the pattern="/welcome/*", the login is successful and the user sees the page. In both the other options, the 403 Access Denied page appears. The user does have 'ADMIN' privileges)
How does Spring security process logout? I have the following code in my welcome.jsp file:
<c:url value="/logout" var="logoutUrl" />
<form action="${logoutUrl}" method="GET" id="logoutForm">
<input type="hidden" name="${_csrf.parameterName}"
value="${_csrf.token}" />
</form>
<script>
function formSubmit() {
document.getElementById("logoutForm").submit();
}
</script>
<c:if test="${pageContext.request.userPrincipal.name != null}">
<h2>
User : ${pageContext.request.userPrincipal.name} | Logout
</h2>
</c:if>
and this in my controller:
#RequestMapping(value = "/logout", method = RequestMethod.GET)
public String logoutPage(HttpServletRequest request, HttpServletResponse response) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) {
new SecurityContextLogoutHandler().logout(request, response, auth);
}
return "redirect:/login?logout";
}
The page redirects correctly and displays the "logout successful" page but if I change the URL to go to "/welcome" again, it shows me the page. Shouldn't it display the 403 - Access Denied page?
About Ant Path matchers in Spring Security
The main role of using Ant-style syntax that you've mentioned is to resolve what exactly paths are valid.
The mapping matches URLs using the following rules:
? matches one character
* matches zero or more characters
** matches zero or more directories in a path
Regarding your cases:
/welcome/* - this could be valid for URLs like /welcome/hello or /welcome/#hello, /welcome/?abc=123
/welcome* - valid are /welcome?abc=123, /welcome#abc=123.
/welcome/** - valid case is /welcome/hello/bye?abc=123.
More on this could be found at Spring Documentation.
Logout action
I assume that you are using xml-configuration for security. Anyway this could be modified for usage of pure Java-configuration.
In app-security.xml should be something like this below:
<http use-expressions="true"
disable-url-rewriting="true">
<http-basic />
<!-- other configurations -->
<intercept-url pattern="/login**" access="isAnonymous()"/>
<intercept-url pattern="/**" access="isAuthenticated()"/>
<!-- other configurations -->
<logout logout-url="/logout"
logout-success-url="/login"/>
</http>
And somewhere in index.html file:
<a href="<c:url value="/logout" />" id="item-btn-logout">
<i class="icon-off"></i> Logout
</a>
The most important part is URL: /logout.
I have following code for spring security but it does not work. When I open log-in page and enter username/password which is admin#myproject.com / secret, following error message will be shown. Once username/password are entered following with be added to the address ?error=1, even if I remove it manually and refresh the page message does not go. Nothing is shown in console.
Your login attempt was not successful due to
Bad credentials.
spring-security.xml
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.2.xsd">
<beans:import resource='login-service.xml' />
<http auto-config="true" access-denied-page="/notFound.jsp"
use-expressions="true">
<intercept-url pattern="/" access="permitAll" />
<intercept-url pattern="/member**" access="hasRole('ROLE_MEMBER')" />
<form-login login-page="/signin" default-target-url="/index"
authentication-failure-url="/signin?error=1" />
<logout logout-success-url="/login?logout" />
<csrf />
</http>
<authentication-manager>
<authentication-provider>
<user-service> <user name="admin#myproject.com" password="secret"
authorities="ROLE_ADMIN"/>
<user name="user#yahoo.com" password="secret" authorities="ROLE_USER"/>
</user-service>
</authentication-provider>
</authentication-manager>
</beans:beans>
The form has following code, it seems like SPRING_SECURITY_LAST_EXCEPTIONis not empty even before submitting the form.
<c:if test="${not empty SPRING_SECURITY_LAST_EXCEPTION}">
<font color="red"> Your login attempt was not successful due
to <br />
<br /> <c:out value="${SPRING_SECURITY_LAST_EXCEPTION.message}" />.
</font>
</c:if>
<form id="form-login" role="form" method="post"
action="<c:url value='/j_spring_security_check' />"
class="relative form form-default">
<input type="hidden" name="${_csrf.parameterName}"
value="${_csrf.token}" />
I am not sure why, but the same code returns following error now
Your login attempt was not successful due to
Authentication method not supported: GET.
You need to allow everyone to access your /signin page, even if he is not authenticated.
<intercept-url pattern="/signin" access="permitAll" />
I wrote this answer before the question was changed the first time, at a time where the question was (it is still the title): "Spring-security shows 'Bad Credentials' even before submitting the form"
<intercept-url pattern="/member**" access="hasRole('ROLE_MEMBER')" />
<user name="user#yahoo.com" password="secret" authorities="ROLE_USER"/>
Above configs have two different Role names ROLE_MEMBER and ROLE_USER
UPDATE
Since Authentication method not supported: GET, can you try allowing GET.
<bean id="authenticationFilter"
class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter"
p:postOnly="false" />
And the following change is also required in web.xml
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
Hope this helps
The SPRING_SECURITY_LAST_EXCEPTION stays in the session even if you refresh the page. You need check for the error parameter:
<c:if test="${(not empty param.error) && (not empty SPRING_SECURITY_LAST_EXCEPTION)}">
I guess your login controller method is doing redirect or forward, and then tries to send a HTTP GET request to the login URL with the user name and password as query parameters. It is generally considered bad practice to send credentials as URL parameters and that's why is not allowed. It be should send a HTTP POST instead.
If you will want to stay with GET, you could bypass the check by using a request wrapper which returns HTTP POST instead of HTTP GET for getMethod.
Updated answer from #tharingu_DG should work, but it is still technically equivalent to sending unencrypted authentication credentials since anyone who steals it can use it to authenticate.
I'm trying to write my test spring security application with mkyong examples.
Spring Security: 4.0.0.RC1
Spring: 4.1.4.RELEASE
I have the following security config:
<http auto-config="true">
<intercept-url pattern="/admin**"
access="hasRole('ADMIN')"/>
<form-login authentication-failure-url="/?auth_error"
username-parameter="user"
password-parameter="password"
login-page="/"
default-target-url="/?OK"/>
<!-- <csrf/> -->
</http>
<authentication-manager>
<authentication-provider>
<user-service>
<user name="mkyong" password="123456" authorities="ADMIN" />
</user-service>
</authentication-provider>
</authentication-manager>
login-page:
<html>
<body>
<form method="POST">
<label for="user">User: </label>
<input type="text" id="user" name="user" /> </br>
<label for="password">Password: </label>
<input type="text" name="password" id="password" /> </br>
<input type="submit" />
</form>
</body>
</html>
Now, when I try to login I get 403 error page:
Invalid CSRF Token 'null' was found on the request parameter
'_csrf' or header 'X-CSRF-TOKEN'.
description:
Access to the specified resource (Invalid CSRF Token 'null' was
found on the request parameter '_csrf' or header 'X-CSRF-TOKEN'.) has been
forbidden.
What's wrong, how can I fix that? I commented csrf in the config, but the error-message has to do with the csrf.
I had the same problem. I use thymeleaf and Spring boot, and got the CSRF token issue when I try to post data in a form.
Here is my working solution:
Add this hidden input:
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
In your WebSecurityConfig (which extends WebSecurityConfigurerAdapter), add a method:
private CsrfTokenRepository csrfTokenRepository()
{
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setSessionAttributeName("_csrf");
return repository;
}
and add code in method configure():
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf()
.csrfTokenRepository(csrfTokenRepository())
I spent lot of time on this problem. Hope it can help someone who has the same problem.
If you must disable it...
In Spring Security 4, CSRF is enabled by default when using the XML configuration. Previously it was only enabled by default for the Java-based configuration.
According to Section 14.4.2 of the Spring Security Documentation:
As of Spring Security 4.0, CSRF protection is enabled by default with XML configuration. If you would like to disable CSRF protection, the corresponding XML configuration can be seen below.
<http>
...
<csrf disabled="true"/>
...
</http>
Disabling CSRF protection sounds like a bad idea, no?
If you use Spring's Form Tag library the CSRF token will be automatically included. It will also HTML Escape form element values, which makes your site safer against XSS, and more correct.
<%# taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<form:form>
<form:input...
</form:form>
Otherwise, add this to your form:
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
To #St.Antario,
Please use this code to enable CSRF in your code
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("*/*").authorizeRequests()
.antMatchers("/", "/login**").permitAll()
.anyRequest().authenticated()
.and().csrf().csrfTokenRepository(csrfTokenRepository())
.and().addFilterAfter(csrfHeaderFilter(), SessionManagementFilter.class);
}
private Filter csrfHeaderFilter() {
return new OncePerRequestFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
if (csrf != null) {
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie == null || token != null
&& !token.equals(cookie.getValue())) {
// Token is being added to the XSRF-TOKEN cookie.
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
};
}
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
//repository.setSessionAttributeName(("X-XSRF-TOKEN"));
return repository;
}
}
spring-security.xml
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xsi:schemaLocation="
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<http pattern="/resources/**" security="none" />
<http use-expressions="true">
<intercept-url pattern="/login*" access="isAnonymous()" />
<intercept-url pattern="/**" access="isAuthenticated()"/>
<form-login
login-page="/login"
default-target-url="/home"
authentication-failure-url="/login?error=true" />
<logout
logout-success-url="/login"
delete-cookies="JSESSIONID" />
</http>
<authentication-manager>
<authentication-provider>
<user-service>
<user name="carlos" password="123" authorities="ROLE_USER" />
</user-service>
</authentication-provider>
</authentication-manager>
</beans:beans>
web.xml
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml, /WEB-INF/spring-security.xml</param-value>
</context-param>
add add jsp login
<%#page session="true"%>
and input hidden:
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
What is your login page named
Is it ending with .jsp
If the login page is not ending with .jsp Spring framework doesn't evaluate the JSP or EL expressions
My goal is to have a link on the page that returns the user to the originating site.I am starting from the spring saml sample http://projects.spring.io/spring-security-saml/ and am adding a new function to the index page.
my saml-servlet.xml and securityContext.xml both have
<context:component-scan base-package="com.home.saml.sp"/>
my returnController.java in the package com.home.saml.sp
#Controller
public class ReturnController {
#RequestMapping(value = "/redirect", method = RequestMethod.POST)
public String redirect() {
String redirectUrl = "http://www.home.com";
return "redirect:"+ redirectUrl;
}
}
and my index.jsp adds
<form method="POST" action="/redirect">
<table>
<tr>
<td><input type="submit" value="Redirect * Page" /></td>
</tr>
</table>
</form>
The saml servlet processes URLs /saml/web/* and therefore skips your /redirect controller. You will need to change the saml servlet mapping in web.xml to:
<servlet-mapping>
<servlet-name>saml</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
Please note that this will break the metadata administration UI in the sample application. You would need to replace the current security for admin UI with:
<!-- Security for the administration UI -->
<security:http pattern="/metadata/**" access-denied-page="/metadata/login">
<security:form-login login-processing-url="/metadata/login_check" login-page="/metadata/login" default-target-url="/metadata"/>
<security:intercept-url pattern="/metadata/login" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<security:intercept-url pattern="/metadata/**" access="ROLE_ADMIN"/>
<security:custom-filter before="FIRST" ref="metadataGeneratorFilter"/>
</security:http>
Then remove all /saml/web prefixes in JSPs and change adminLogin.jsp to use /metadata/login_check in the loginForm.
I just setup Spring Security and it's working great. I can log in and out using my custom form. I have logged-in then out multiple times successively with no problem. Oddly, if I try to login with the wrong password, I can no longer log in again - it takes me to the "loginFailed" page every time I try to login after that. Does anyone know how to fix this?
Here is my security-config.xml:
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.0.3.xsd">
<http use-expressions="true">
<intercept-url pattern="/secureA.htm" access="isAuthenticated()" />
<intercept-url pattern="/secureB.htm" access="isAuthenticated()" />
<form-login login-page="/login.htm" default-target-url="/secureA.htm"
authentication-failure-url="/loginFailed.htm"/>
<logout logout-success-url="/login" />
</http>
<authentication-manager>
<authentication-provider>
<password-encoder hash="sha">
</password-encoder>
<user-service>
<user name="user" password="pass"
authorities="ROLE_USER, ROLE_ADMIN" />
</user-service>
</authentication-provider>
</authentication-manager>
</beans:beans>
My login form:
<form action="<c:url value='j_spring_security_check' />" method="post">
Username: <input type="text" name="j_username" /><br/>
Password: <input type="password" name="j_password" /><br/>
<input type="submit" value="Login" /><br/>
</form>
My login controller:
#Controller
public class LoginController {
#RequestMapping(value = "/login", method = RequestMethod.GET)
public String welcome(ModelMap m) {
return "login";
}
#RequestMapping(value = "/loginFailed", method = RequestMethod.GET)
public String failed(ModelMap m) {
m.addAttribute("error", "Invalid username or password");
return "login";
}
#RequestMapping("logout.htm")
public String logout(ModelMap m) {
return "redirect:login.htm";
}
}
If you're returning to the same page you logged into, the model is carried through. Since you're adding an attribute to that model, you're presumably checking to see if that attribute is set on the view page. Once it gets set, nothing will unset it until your session objects are cleared.
To confirm, change this:
#RequestMapping(value = "/login", method = RequestMethod.GET)
public String welcome(ModelMap m) {
System.out.println(m.getAttribute("error");
return "login";
}
And then check your console.
Well, I don't know what the problem was, but I was able to resolve it. I just found an older SO post: Spring security blocks user after failed login. Timh's answer of switching to Spring 3.0.7.RELEASE (from 3.0.6) worked for me. Sorry for double-posting.
If anyone know why this was happening in 3.0.6, I'd be interested to know.
This is because:
https://jira.springsource.org/browse/SEC-1493
org.springframework.security.core.userdetails.eraseCredentials()
is being invoked on authentication.
Solution should be parameter erase-credentials="false" for authentication-manager, but apparently it doesn't worked for me in Spring 3.1.0 or 3.1.3.
Work-around would be to re-load users' details on each authentication request.