Fork me on GitHub

Programming Design Notes

使用 Spring Security 3 來保護頁面

| Comments

Spring Security 官方文檔內只有很簡單的教學,教學內將所有 username 和 password 放到 xml 文件內,在真實的環境很少會這樣做,大多數也會經由資料庫去存取用戶資料,在這裡我做一個簡單的範例,雖然這範例沒有使用到資料庫,而只是簡單的制作一個用戶認証的 class,但憑這個 class 你可以經由資料庫或其他方式去存取用戶資料。

首先我們要制作 3 個 JSP 檔案

  1. index.jsp (主頁面)
  2. login.jsp (login 表格頁面)
  3. secure/protected.jsp (受保護的 JSP 頁面)

index.jsp
<%@page contentType="text/html" pageEncoding="UTF-8" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Index Page</title>
</head>
<body>
<a href="secure/protected.jsp">Click here to view secure page</a>
</body>
</html>

login.jsp
<?xml version="1.0" encoding="UTF-8"?>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" %>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Login Page</title>
</head>
<body>
<c:if test="${param.error != null}">
<h2>Username or password wrong!</h2>
</c:if>
<form method="post" action="j_spring_security_check">
<label>
username:
</label>
<input type="text" name="j_username"/>
<br/>
<label>
password:
</label>
<input type="password" name="j_password"/>
<br/>
<input type="checkbox" name="_spring_security_remember_me"/>remember me
<br/>
<input type="submit"/>
</form>
</body>
</html>

secure/protected.jsp
<?xml version="1.0" encoding="UTF-8"?>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Protected Page</title>
</head>
<body>
<h1>Hello World! This is secure page!</h1>
<a href="../j_spring_security_logout">Logout</a>
</body>
</html>

現在設定 web.xml
在 web.xml 加入 Spring 的 ApplicationContext 檔案位置
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/security-config.xml
</param-value>
</context-param>

再加入 Spring ApplicationContext Listerner
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

再加入 Spring Security 的 Filter
<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.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5">
<display-name>SpringSecurity</display-name>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/security-config.xml
</param-value>
</context-param>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

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

現在制作 Spring Security 的設定檔
/WEB-INF/security-config.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.0.xsd">
<http use-expressions="true" auto-config="true">
<intercept-url pattern="/secure/protected.jsp" access="hasRole('ROLE_USER')"/>
<intercept-url pattern="/login.jsp" access="isAnonymous()"/>
<intercept-url pattern="/**" access="permitAll"/>
<form-login login-processing-url="/j_spring_security_check"
login-page="/login.jsp" default-target-url="/index.jsp"
authentication-failure-url="/login.jsp?error=1"/>
<logout logout-url="/j_spring_security_logout"/>
</http>
</beans:beans>

下面的描述是:

  1. 將 /secure/protected.jsp 設為 ROLE_USER 才可以進入。
  2. 未 login 的用戶才可以進入
  3. 其他頁面一律可以進入

<intercept-url pattern="/secure/protected.jsp" access="hasRole('ROLE_USER')"/>
<intercept-url pattern="/login.jsp" access="isAnonymous()"/>
<intercept-url pattern="/**" access="permitAll"/>

現在 Create 一個 User Class 並實作 UserDetails Interface。
com.blogspot.lawpronotes.security.User
package com.blogspot.lawpronotes.security;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

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

public class User implements UserDetails {

private static final long serialVersionUID = 1L;

private String name;
private String password;

public User(String name, String password) {
this.name = name;
this.password = password;
}

@Override
public Collection<GrantedAuthority> getAuthorities() {
// TODO Auto-generated method stub
List<GrantedAuthority> grantedAuthorities = new ArrayList<GrantedAuthority>();
grantedAuthorities.add(new GrantedAuthorityImpl("ROLE_USER"));
return grantedAuthorities;
}

@Override
public String getPassword() {
// TODO Auto-generated method stub
return password;
}

@Override
public String getUsername() {
// TODO Auto-generated method stub
return name;
}

@Override
public boolean isAccountNonExpired() {
// TODO Auto-generated method stub
return true;
}

@Override
public boolean isAccountNonLocked() {
// TODO Auto-generated method stub
return true;
}

@Override
public boolean isCredentialsNonExpired() {
// TODO Auto-generated method stub
return true;
}

@Override
public boolean isEnabled() {
// TODO Auto-generated method stub
return true;
}

}

User 這個 Class 作用是給 Spring Security 儲存 User。

現在制作一個 SecurityManager 的 Class 並實作 UserDetailsService Interface。
com.blogspot.lawpronotes.security.SecurityManager
package com.blogspot.lawpronotes.security;

import java.util.ArrayList;

import org.springframework.dao.DataAccessException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

public class SecurityManager implements UserDetailsService {

@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {
// TODO Auto-generated method stub
ArrayList<User> userList = new ArrayList<User>();
userList.add(new User("Lawrence", "123456"));
userList.add(new User("Tom", "654321"));
userList.add(new User("Terry", "321456"));
for (int i = 0; i < userList.size(); i++) {
User user = (User) userList.get(i);
if (user.getUsername().equals(username))
return user;
}
throw new UsernameNotFoundException("User " + username
+ " has no GrantedAuthority");
}
}

loadUserByUsername 是認証用戶的主要部份,成功配對到用戶名稱便返回一個 UserDetails 的對像,並交由 Spring Security 再去配對密碼是否正確,所以我們是看不到用戶密碼的。
這這個 method 內,我只是將用戶放到 List 中再配對起來,各位可以選擇用什麼方法去存取用戶資料。

現在回到 Spring Security 的設定檔去加入 SecurityManager 這個自定的 Class。
加入以下 xml 到 security-config.xml 內。
<authentication-manager>
<authentication-provider user-service-ref="securityManager"/>
</authentication-manager>
<beans:bean id="securityManager" class="com.blogspot.lawpronotes.security.SecurityManager"/>

完整 security-config.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.0.xsd">
<http use-expressions="true" auto-config="true">
<intercept-url pattern="/secure/protected.jsp" access="hasRole('ROLE_USER')"/>
<intercept-url pattern="/login.jsp" access="isAnonymous()"/>
<intercept-url pattern="/**" access="permitAll"/>
<form-login login-processing-url="/j_spring_security_check"
login-page="/login.jsp" default-target-url="/index.jsp"
authentication-failure-url="/login.jsp?error=1"/>
<logout logout-url="/j_spring_security_logout"/>
</http>
<authentication-manager>
<authentication-provider user-service-ref="securityManager"/>
</authentication-manager>
<beans:bean id="securityManager" class="com.blogspot.lawpronotes.security.SecurityManager"/>
</beans:beans>

現在可以試試效果,直接進入 /secure/protected.jsp 會彈到 login.jsp。
只有經過認証的用戶才可進入。

範例下載: SpringSecurity.war

相關書籍: Spring Recipes: A Problem-Solution ApproachSpring in ActionProfessional Java Development with the Spring Framework