Spring Security. Страница Login

июля
03
2012
Метки: java mvc security spring

Содержание

В этой статье будет рассказано как привести к произвольному виду страницу логина в Spring Security версии 3.x.

В этой статье как основа используется пример из предыдущей статьи "Введение в Spring Security. Hello World!". Создайте проект, следуя инструкциям из статьи, или скачайте файл проекта для его использования в этой статье.

В предыдущей статье упоминалось, что форма логина генерируется автоматически фреймворком Spring Security. Поэтому в проекте отсутствовали jsp-файлы, отвечающие за страницу логина. Тем не менее, у Вас есть возможность привести эту страницу к любому виду, изменив конфигурацию http в файле application-security.xml следующим образом:


<http auto-config="true">
	<intercept-url pattern="/secure/**" access="ROLE_USER" />
	<intercept-url pattern="/admin/**" access="ROLE_ADMIN" />
	<intercept-url pattern="/login.jsp*" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
	<form-login login-page="/signin"
			authentication-failure-url="/signin-failure" default-target-url="/" />
</http>

В случае неверного логина/пароля пользователь будет автоматически перенаправлен на страницу /signin-failure, что описывается атрибутом authentication-failure-url тега form-login.

Далее нам также понадобится изменить блок authentication-manager. Вместо:


<authentication-manager>
	<authentication-provider>
		<user-service>
			<user name="admin" password="adminpassword" authorities="ROLE_USER, ROLE_ADMIN" />
			<user name="user" password="userpassword" authorities="ROLE_USER" />
		</user-service>
	</authentication-provider>
</authentication-manager>

добавьте следующий код:


<authentication-manager>
	<authentication-provider user-service-ref="customUserDetailsService">
	</authentication-provider>
</authentication-manager>

Упомянутый сервис customUserDetailsService нужен для обработки данных (логина/пароля), полученных от пользователя. Исходный код класса CustomUserDetailsService будет представлен позже, а пока приведем измененные файлы конфигурации.

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>
			/WEB-INF/spring/root-context.xml
			/WEB-INF/spring/application-security.xml
		</param-value>
	</context-param>
	
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

	<servlet>
		<servlet-name>appServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
		
	<servlet-mapping>
		<servlet-name>appServlet</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>

	<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>

</web-app>

root-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
	
	<annotation-driven />

	<resources mapping="/resources/**" location="/resources/" />

	<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<beans:property name="prefix" value="/WEB-INF/views/" />
		<beans:property name="suffix" value=".jsp" />
	</beans:bean>
	
	<context:component-scan base-package="com.seostella.springsecuritylogin" />
			
</beans:beans>

application-security.xml

<?xml version="1.0" encoding="UTF-8"?>
<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.1.xsd">

	<http pattern="/css/**" security="none" />

	<http auto-config="true">
		<intercept-url pattern="/secure/**" access="ROLE_USER" />
		<intercept-url pattern="/admin/**" access="ROLE_ADMIN" />
		<intercept-url pattern="/login" access="IS_AUTHENTICATED_ANONYMOUSLY" />
		<form-login login-page="/signin"
			authentication-failure-url="/signin-failure" default-target-url="/" />
	</http>

	<authentication-manager>
		<authentication-provider user-service-ref="customUserDetailsService">
		</authentication-provider>
	</authentication-manager>
	
</beans:beans>

servlet-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

</beans:beans>

Итак, возвращаемся к CustomUserDetailsService, его код выглядит следующим образом:


package com.seostella.springsecuritylogin.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
public class CustomUserDetailsService implements UserDetailsService{
	
	@Autowired
	private UserManager userManager;
	
	public UserDetails loadUserByUsername(String username)
			throws UsernameNotFoundException {
		return userManager.getUser(username);
	}

}

Пользовательский сервис должен быть унаследован от интерфейса UserDetailsService. Единственный метод этого интерфейса, который нужно реализовать, называется loadUserByUsername. Этот метод должен возвращать объект, который реализует интерфейс UserDetails. Такой класс у нас будет называться User.


package com.seostella.springsecuritylogin.domain;

import java.util.Collection;
import java.util.HashSet;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

public class User implements UserDetails {
	private static final long serialVersionUID = 8266525488057072269L;
	private String username;
	private String password;
	private Collection<GrantedAuthority> authorities;

	public User(String username, String password, String roles) {
		super();
		this.username = username;
		this.password = password;
		this.setRoles(roles);
	}

	public void setRoles(String roles) {
		this.authorities = new HashSet<GrantedAuthority>();
		for (final String role : roles.split(",")) {
			if (role != null && !"".equals(role.trim())) {
				GrantedAuthority grandAuthority = new GrantedAuthority() {
					private static final long serialVersionUID = 3958183417696804555L;

					public String getAuthority() {
						return role.trim();
					}
				};
				this.authorities.add(grandAuthority);
			}
		}
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public Collection<? extends GrantedAuthority> getAuthorities() {
		return this.authorities;
	}

	public String getPassword() {
		return password;
	}

	public String getUsername() {
		return username;
	}

	public boolean isAccountNonExpired() {
		return true;
	}

	public boolean isAccountNonLocked() {
		return true;
	}

	public boolean isCredentialsNonExpired() {
		return true;
	}

	public boolean isEnabled() {
		return true;
	}
}

Код класса UserManager:


package com.seostella.springsecuritylogin.service;

import java.util.HashMap;

import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import com.seostella.springsecuritylogin.domain.User;

@Service
public class UserManager {
	private HashMap<String, User> users;

	public UserManager() {
		users = new HashMap<String, User>();
		users.put("john", new User("john", "1", "ROLE_USER"));
		users.put("bob", new User("bob", "2", "ROLE_USER, ROLE_ADMIN"));
	}
	
	public User getUser(String username) throws UsernameNotFoundException{
		if( !users.containsKey( username ) ){
			throw new UsernameNotFoundException( username + " not found" );
		}
		
		return users.get( username );		
	}
}

В конструкторе представленного сервиса создается два объекта User с именами john и bob. Конечно, на месте статического создания объектов можно было бы сделать метод для извлечения пользователей из базы данных. Но это привело бы к усложнению примера, чего мы пытаемся избежать.

Контроллер для обработки действий /signin и /signin-failure представлен ниже:


package com.seostella.springsecuritylogin.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class SigninController {
	@RequestMapping(value = "/signin", method = RequestMethod.GET)
	public String signin() {
		return "user/signin";
	}
	
	@RequestMapping(value = "/signin-failure", method = RequestMethod.GET)
	public String signinFailure() {
		return "user/signin_failure";
	}
	
}

И, наконец, jsp-файл signin.jsp, содержащий форму логина:


<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring"%>
<%@ page session="true"%>
<html>
<head>
<title>Sign In</title>
</head>
<body>
	<h1>Spring Security - Sign In</h1>

	<div style="color: red">${message}</div>
	
	<form class="login-form" action="j_spring_security_check" method="post">
			<label for="j_username">Username: </label>
		 	<input id="j_username" name="j_username" size="20" maxlength="50" type="text" />

			<label for="j_password">Password: </label>
			<input id="j_password" name="j_password" size="20" maxlength="50" type="password" />
			
			<input type="submit" value="Login" />
	</form>
</body>
</html>

Несколько пояснений по поводу формы. Первое, действие формы (атрибут action) должно быть j_spring_security_check. Второе, название полей для логина и пароля должны быть j_username и j_password соответственно.

Файл signin_failure.jsp:


<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring"%>
<%@ page session="true"%>
<html>
<head>
<title>Sign In</title>
</head>
<body>
	<h1>Spring Security - Sign In Failure</h1>
</body>
</html>

Если Вы хотите использовать стандартный HTTP-диалог для авторизации и отказаться от формы логина, измените http-блок конфигурации безопасности следующим образом:


<http auto-config="true">
	<intercept-url pattern="/secure/**" access="ROLE_USER" />
	<intercept-url pattern="/admin/**" access="ROLE_ADMIN" />
<!-- 		<intercept-url pattern="/login" access="IS_AUTHENTICATED_ANONYMOUSLY" /> -->
<!-- 		<form-login login-page="/signin" -->
<!-- 			authentication-failure-url="/signin-failure" default-target-url="/" /> -->
	<http-basic />
</http>

Если Вы хотите чтобы пользователь всегда перенаправлялся на страницу, указанную в атрибуте default-target-url - измените http-блок следующим образом:


<http auto-config="true">
	<intercept-url pattern="/secure/**" access="ROLE_USER" />
	<intercept-url pattern="/admin/**" access="ROLE_ADMIN" />
	<intercept-url pattern="/login.jsp*" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
	<form-login login-page="/signin"
			authentication-failure-url="/signin-failure" default-target-url="/" always-use-default-target="true" />
</http>

Скачать проект, демонстрирующий вышеприведенный пример, Вы можете используя следующую ссылку Скачать spring-security-login.zip

< Введение в Spring Security. Hello World! Как получить пользователя в Spring Security >

Комментарии (3)

Velesey
10 августа 2012 г. 11:40
Большое спасибо! Здорово помогло)
msangel
20 октября 2013 г. 5:36
web.xml от своего предшественника отличается тем, что убраны комментарии.
Не очень функциональное изменение. Я думаю не стоило бы даже писать, что он изменился, или написали бы, что вот он конкретно(дальше не дочитал еще) - не изменился.
msangel
20 октября 2013 г. 5:41
а в идеале - чтобы вы подсвечивали изменившиеся строки
чтоб я после предыдущей статьи сюда пришел и не копировал бессмысленно файлы/сравнивал с предыдущими
а просто увидел разницу
Вы должны войти под своим аккаунтом чтобы оставлять комментарии