下面咱們將實現關於Spring Security3的一系列教程.
最終的目標是整合Spring Security + Spring3MVC
完成相似於SpringSide3中mini-web的功能.
Spring Security是什麼?
html
引用java
Spring Security,這是一種基於Spring AOP和Servlet過濾器的安全框架。它提供全面的安全性解決方案,同時在Web請求級和方法調用級處理身份確認和受權。在Spring Framework基礎上,Spring Security充分利用了依賴注入(DI,Dependency Injection)和麪向切面技術。web
關於Spring Security學習的資料.
最重要,最齊全的中文資料固然是family168的中文文檔
Spring Security2參考文檔
Spring Security3 參考文檔
附件包含了一個很好的初入門的PDF教程.
最好是花30分鐘先照着PDF上的教程一步一步的操做.
雖然沒有實際的應用價值,但對初學者認識SpringSecurity3頗有幫助.
咱們的項目目錄結構最終是:
須要添加的jar包:
咱們先實現一個controller:
MainController.java
spring
-
package org.liukai.tutorial.controller; express
-
-
import org.apache.log4j.Logger; apache
-
import org.springframework.stereotype.Controller; 編程
-
import org.springframework.web.bind.annotation.RequestMapping; spring-mvc
-
import org.springframework.web.bind.annotation.RequestMethod; tomcat
-
-
@Controller
-
@RequestMapping("/main")
-
public class MainController {
-
protected static Logger logger = Logger.getLogger("controller");
-
-
/**
-
* 跳轉到commonpage頁面
-
*
-
* @return
-
*/
-
@RequestMapping(value = "/common", method = RequestMethod.GET)
-
public String getCommonPage() {
-
logger.debug("Received request to show common page");
-
return "commonpage";
-
}
-
-
/**
-
* 跳轉到adminpage頁面
-
*
-
* @return
-
*/
-
@RequestMapping(value = "/admin", method = RequestMethod.GET)
-
public String getAadminPage() {
-
logger.debug("Received request to show admin page");
-
return "adminpage";
-
-
}
-
-
}
該controller有兩個mapping映射:
引用
main/common
main/admin
如今咱們將同過Spring Security3框架實現成功登錄的人都能訪問到main/common.
但只有擁有admin權限的用戶才能訪問main/admin.
咱們先在web.xml中開啓Spring3MVC和SpringSecurity3.
web.xml
-
<?xml version="1.0" encoding="UTF-8"?>
-
<web-app id="WebApp_ID" version="2.4"
-
xmlns="http://java.sun.com/xml/ns/j2ee"
-
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
-
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
-
-
<!-- SpringSecurity必須的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>
-
-
<context-param>
-
<param-name>contextConfigLocation</param-name>
-
<param-value>
-
/WEB-INF/spring-security.xml
-
/WEB-INF/applicationContext.xml
-
</param-value>
-
</context-param>
-
-
<servlet>
-
<servlet-name>spring</servlet-name>
-
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
-
<load-on-startup>1</load-on-startup>
-
</servlet>
-
-
<servlet-mapping>
-
<servlet-name>spring</servlet-name>
-
<url-pattern>/</url-pattern>
-
</servlet-mapping>
-
-
<listener>
-
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
-
</listener>
-
-
</web-app>
要啓用SpringSecurity3,咱們須要完成如下兩步:
1.在web.xml中聲明DelegatingFilterProxy.
-
<filter-mapping>
-
<filter-name>springSecurityFilterChain</filter-name>
-
<url-pattern>/*</url-pattern>
-
</filter-mapping>
表示項目中全部路徑的資源都要通過SpringSecurity.
2.導入指定的SpringSecurity配置 :spring-security.xml
關於spring-security.xml的配置.
咱們把這個放到後面配置.以便更詳細的講解.
注意一點.最好是將DelegatingFilterProxy寫在DispatcherServlet以前.不然
SpringSecurity可能不會正常工做.
在web.xml中咱們定義servlet:spring.
按照慣例,咱們必須聲明一個spring-servle.xml
spring-servle.xml
-
<?xml version="1.0" encoding="UTF-8"?>
-
<beans xmlns="http://www.springframework.org/schema/beans"
-
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
-
xsi:schemaLocation="http://www.springframework.org/schema/beans
-
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
-
<!-- 定義一個視圖解析器 -->
-
<bean id="viewResolver"
-
class="org.springframework.web.servlet.view.InternalResourceViewResolver"
-
p:prefix="/WEB-INF/jsp/" p:suffix=".jsp" />
-
-
</beans>
這個XML配置聲明一個視圖解析器.在控制器中會根據JSP名映射到/ WEB-INF/jsp中相應的位置.
而後建立一個applicationContext.xml.
applicationContext.xml.
-
<?xml version="1.0" encoding="UTF-8"?>
-
<beans xmlns="http://www.springframework.org/schema/beans"
-
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-
xmlns:context="http://www.springframework.org/schema/context"
-
xmlns:mvc="http://www.springframework.org/schema/mvc"
-
xsi:schemaLocation="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
-
http://www.springframework.org/schema/mvc
-
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
-
-
<!-- 激活spring的註解. -->
-
<context:annotation-config />
-
-
<!-- 掃描註解組件而且自動的注入spring beans中.
-
例如,他會掃描@Controller 和@Service下的文件.因此確保此base-package設置正確. -->
-
<context:component-scan base-package="org.liukai.tutorial" />
-
-
<!-- 配置註解驅動的Spring MVC Controller 的編程模型.注:次標籤只在 Servlet MVC工做! -->
-
<mvc:annotation-driven />
-
-
</beans>
接着是建立JSP頁面
commonpage.jsp
-
<%@ page language="java" contentType="text/html; charset=UTF-8"
-
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>Insert title here</title>
-
</head>
-
<body>
-
<h1>Common Page</h1>
-
<p>每一個人都能訪問的頁面.</p>
-
<a href="/spring3-security-integration/main/admin"> Go AdminPage </a>
-
<br />
-
<a href="/spring3-security-integration/auth/login">退出登陸</a>
-
-
</body>
-
</html>
adminpage.jsp
-
<%@ page language="java" contentType="text/html; charset=UTF-8"
-
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>Insert title here</title>
-
</head>
-
<body>
-
<h1>Admin Page</h1>
-
<p>管理員頁面</p>
-
<a href="/spring3-security-integration/auth/login">退出登陸</a>
-
</body>
-
</html>
這兩個JSP對應着
固然還有登錄頁面和拒絕訪問頁面
loginpage.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 language="java" contentType="text/html; charset=UTF-8"
-
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>Insert title here</title>
-
</head>
-
<body>
-
-
<h1>Login</h1>
-
-
<div id="login-error">${error}</div>
-
-
<form action="../j_spring_security_check" method="post">
-
-
<p>
-
<label for="j_username">Username</label> <input id="j_username"
-
name="j_username" type="text" />
-
</p>
-
-
<p>
-
<label for="j_password">Password</label> <input id="j_password"
-
name="j_password" type="password" />
-
</p>
-
-
<input type="submit" value="Login" />
-
-
</form>
-
-
</body>
-
</html>
deniedpage.jsp
-
<%@ page language="java" contentType="text/html; charset=UTF-8"
-
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>Insert title here</title>
-
</head>
-
<body>
-
<h1>你的權限不夠!</h1>
-
<p>只有擁有Admin權限才能訪問!</p>
-
<a href="/spring3-security-integration/auth/login">退出登陸</a>
-
</body>
-
</html>
還有一個controller用於映射上面兩個JSP頁面..
LoginLogoutController.java
-
package org.liukai.tutorial.controller;
-
-
import org.apache.log4j.Logger;
-
import org.springframework.stereotype.Controller;
-
import org.springframework.ui.ModelMap;
-
import org.springframework.web.bind.annotation.RequestMapping;
-
import org.springframework.web.bind.annotation.RequestMethod;
-
import org.springframework.web.bind.annotation.RequestParam;
-
-
@Controller
-
@RequestMapping("auth")
-
public class LoginLogoutController {
-
-
protected static Logger logger = Logger.getLogger("controller");
-
-
/**
-
* 指向登陸頁面
-
*/
-
@RequestMapping(value = "/login", method = RequestMethod.GET)
-
public String getLoginPage(
-
@RequestParam(value = "error", required = false) boolean error,
-
ModelMap model) {
-
-
logger.debug("Received request to show login page");
-
-
if (error == true) {
-
// Assign an error message
-
model.put("error",
-
"You have entered an invalid username or password!");
-
} else {
-
model.put("error", "");
-
}
-
return "loginpage";
-
-
}
-
-
/**
-
* 指定無訪問額權限頁面
-
*
-
* @return
-
*/
-
@RequestMapping(value = "/denied", method = RequestMethod.GET)
-
public String getDeniedPage() {
-
-
logger.debug("Received request to show denied page");
-
-
return "deniedpage";
-
-
}
-
}
該controller實現了兩個映射
引用
auth/login --顯示Login頁面
auth/denied --顯示拒絕訪問頁面
最後,讓咱們看看spring-security.xml的配置
spring-security.xml
-
<?xml version="1.0" encoding="UTF-8"?>
-
<beans xmlns="http://www.springframework.org/schema/beans"
-
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-
xmlns:security="http://www.springframework.org/schema/security"
-
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">
-
-
<!-- Spring-Security 的配置 -->
-
<!-- 注意開啓use-expressions.表示開啓表達式.
-
see:http://www.family168.com/tutorial/springsecurity3/html/el-access.html
-
-->
-
<security:http auto-config="true" use-expressions="true" access-denied-page="/auth/denied" >
-
-
<security:intercept-url pattern="/auth/login" access="permitAll"/>
-
<security:intercept-url pattern="/main/admin" access="hasRole('ROLE_ADMIN')"/>
-
<security:intercept-url pattern="/main/common" access="hasRole('ROLE_USER')"/>
-
-
<security:form-login
-
login-page="/auth/login"
-
authentication-failure-url="/auth/login?error=true"
-
default-target-url="/main/common"/>
-
-
<security:logout
-
invalidate-session="true"
-
logout-success-url="/auth/login"
-
logout-url="/auth/logout"/>
-
-
</security:http>
-
-
<!-- 指定一個自定義的authentication-manager :customUserDetailsService -->
-
<security:authentication-manager>
-
<security:authentication-provider user-service-ref="customUserDetailsService">
-
<security:password-encoder ref="passwordEncoder"/>
-
</security:authentication-provider>
-
</security:authentication-manager>
-
-
<!-- 對密碼進行MD5編碼 -->
-
<bean class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" id="passwordEncoder"/>
-
-
<!--
-
經過 customUserDetailsService,Spring會自動的用戶的訪問級別.
-
也能夠理解成:之後咱們和數據庫操做就是經過customUserDetailsService來進行關聯.
-
-->
-
<bean id="customUserDetailsService" class="org.liukai.tutorial.service.CustomUserDetailsService"/>
-
-
</beans>
在配置中咱們能夠看到三個URL對應的三個權限
-
<security:intercept-url pattern="/auth/login" access="permitAll"/>
-
<security:intercept-url pattern="/main/admin" access="hasRole('ROLE_ADMIN')"/>
-
<security:intercept-url pattern="/main/common" access="hasRole('ROLE_USER')"/>
須要注意的是咱們使用了SpringEL表達式來指定角色的訪問.
如下是表達式對應的用法.
引用
表達式 說明
hasRole([role]) 返回 true 若是當前主體擁有特定角色。
hasAnyRole([role1,role2]) 返回 true 若是當前主體擁有任何一個提供的角色 (使用逗號分隔的字符串隊列)
principal 容許直接訪問主體對象,表示當前用戶
authentication 容許直接訪問當前 Authentication對象 從SecurityContext中得到
permitAll 一直返回true
denyAll 一直返回false
isAnonymous() 若是用戶是一個匿名登陸的用戶 就會返回 true
isRememberMe() 若是用戶是經過remember-me 登陸的用戶 就會返回 true
isAuthenticated() 若是用戶不是匿名用戶就會返回true
isFullyAuthenticated() 若是用戶不是經過匿名也不是經過remember-me登陸的用戶時, 就會返回true。
因此
-
<security:intercept-url pattern="/auth/login" access="permitAll"/>
表示全部的人均可以訪問/auth/login.
-
<security:intercept-url pattern="/main/admin" access="hasRole('ROLE_ADMIN')"/>
-
<security:intercept-url pattern="/main/common" access="hasRole('ROLE_USER')"/>
則表示只有擁有對應的角色才能訪問.
-
<security:form-login
-
login-page="/auth/login"
-
authentication-failure-url="/auth/login?error=true"
-
default-target-url="/main/common"/>
表示經過 /auth/login這個映射進行登陸.
若是驗證失敗則返回一個URL:/auth/login?error=true
若是登陸成功則默認指向:/main/common
-
security:logout
-
invalidate-session="true"
-
logout-success-url="/auth/login"
-
logout-url="/auth/logout"/>
很簡單.咱們開啓了session失效功能.
註銷URL爲:/auth/logout
註銷成功後轉向:/auth/login
-
<!-- 指定一個自定義的authentication-manager :customUserDetailsService -->
-
<security:authentication-manager>
-
<security:authentication-provider user-service-ref="customUserDetailsService">
-
<security:password-encoder ref="passwordEncoder"/>
-
</security:authentication-provider>
-
</security:authentication-manager>
-
-
<!-- 對密碼進行MD5編碼 -->
-
<bean class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" id="passwordEncoder"/>
-
-
<!--
-
經過 customUserDetailsService,Spring會自動的用戶的訪問級別.
-
也能夠理解成:之後咱們和數據庫操做就是經過customUserDetailsService來進行關聯.
-
-->
-
<bean id="customUserDetailsService" class="org.liukai.tutorial.service.CustomUserDetailsService"/>
一個自定義的CustomUserDetailsService,是實現SpringSecurity的UserDetailsService接口,但咱們重寫了他即使於咱們進行數據庫操做.
DbUser.java
-
package org.liukai.tutorial.domain;
-
-
public class DbUser {
-
-
private String username;
-
private String password;
-
private Integer access;
-
-
//getter/setter
-
-
}
經過一個初始化的List來模擬數據庫操做.
UserDao.java
-
package org.liukai.tutorial.dao;
-
-
import java.util.ArrayList;
-
import java.util.List;
-
-
import org.apache.log4j.Logger;
-
import org.liukai.tutorial.domain.DbUser;
-
-
public class UserDao {
-
-
protected static Logger logger = Logger.getLogger("dao");
-
-
public DbUser getDatabase(String username) {
-
-
List<DbUser> users = internalDatabase();
-
-
for (DbUser dbUser : users) {
-
if (dbUser.getUsername().equals(username) == true) {
-
logger.debug("User found");
-
return dbUser;
-
}
-
}
-
logger.error("User does not exist!");
-
throw new RuntimeException("User does not exist!");
-
-
}
-
-
/**
-
* 初始化數據
-
*/
-
private List<DbUser> internalDatabase() {
-
-
List<DbUser> users = new ArrayList<DbUser>();
-
DbUser user = null;
-
-
user = new DbUser();
-
user.setUsername("admin");
-
-
// "admin"通過MD5加密後
-
user.setPassword("21232f297a57a5a743894a0e4a801fc3");
-
user.setAccess(1);
-
-
users.add(user);
-
-
user = new DbUser();
-
user.setUsername("user");
-
-
// "user"通過MD5加密後
-
user.setPassword("ee11cbb19052e40b07aac0ca060c23ee");
-
user.setAccess(2);
-
-
users.add(user);
-
-
return users;
-
-
}
-
}
自定義UserDetailsService .能夠經過繼承UserDetailsService
來達到靈活的自定義UserDetailsService
關於UserDetailsService更多信息. 能夠查看SpringSecurity3文檔
CustomUserDetailsService.java
-
package org.liukai.tutorial.service;
-
-
import java.util.ArrayList;
-
import java.util.Collection;
-
import java.util.List;
-
-
import org.apache.log4j.Logger;
-
import org.liukai.tutorial.dao.UserDao;
-
import org.liukai.tutorial.domain.DbUser;
-
import org.springframework.dao.DataAccessException;
-
import org.springframework.security.core.GrantedAuthority;
-
import org.springframework.security.core.authority.GrantedAuthorityImpl;
-
import org.springframework.security.core.userdetails.User;
-
import org.springframework.security.core.userdetails.UserDetails;
-
import org.springframework.security.core.userdetails.UserDetailsService;
-
import org.springframework.security.core.userdetails.UsernameNotFoundException;
-
-
/**
-
* 一個自定義的service用來和數據庫進行操做. 即之後咱們要經過數據庫保存權限.則須要咱們繼承UserDetailsService
-
*
-
* @author liukai
-
*
-
*/
-
public class CustomUserDetailsService implements UserDetailsService {
-
-
protected static Logger logger = Logger.getLogger("service");
-
-
private UserDao userDAO = new UserDao();
-
-
public UserDetails loadUserByUsername(String username)
-
throws UsernameNotFoundException, DataAccessException {
-
-
UserDetails user = null;
-
-
try {
-
-
// 搜索數據庫以匹配用戶登陸名.
-
// 咱們能夠經過dao使用JDBC來訪問數據庫
-
DbUser dbUser = userDAO.getDatabase(username);
-
-
// Populate the Spring User object with details from the dbUser
-
// Here we just pass the username, password, and access level
-
// getAuthorities() will translate the access level to the correct
-
// role type
-
-
user = new User(dbUser.getUsername(), dbUser.getPassword()
-
.toLowerCase(), true, true, true, true,
-
getAuthorities(dbUser.getAccess()));
-
-
} catch (Exception e) {
-
logger.error("Error in retrieving user");
-
throw new UsernameNotFoundException("Error in retrieving user");
-
}
-
-
return user;
-
}
-
-
/**
-
* 得到訪問角色權限
-
*
-
* @param access
-
* @return
-
*/
-
public Collection<GrantedAuthority> getAuthorities(Integer access) {
-
-
List<GrantedAuthority> authList = new ArrayList<GrantedAuthority>(2);
-
-
// 全部的用戶默認擁有ROLE_USER權限
-
logger.debug("Grant ROLE_USER to this user");
-
authList.add(new GrantedAuthorityImpl("ROLE_USER"));
-
-
// 若是參數access爲1.則擁有ROLE_ADMIN權限
-
if (access.compareTo(1) == 0) {
-
logger.debug("Grant ROLE_ADMIN to this user");
-
authList.add(new GrantedAuthorityImpl("ROLE_ADMIN"));
-
}
-
-
return authList;
-
}
-
}
最後啓動服務器輸入:
http://localhost:8080/spring3-security-integration/auth/login
總結
經過本教程.咱們對SpringSecurity3有了進一步的認識.
主要是瞭解了UserDetailsService的重要做用.
以及實現了模擬自定義數據的登陸.(這點很重要,不少人學習了SpringSecurity殊不知道
如何自定義權限)
此次教程由於內容不少,顯得比較粗糙.不少地方並無詳細的闡明.
後面的教程仍是SpringSecurity.
但咱們將對SpringSecurity3新推出的一些特性進行詳細的說明和理解.
BTW:附件爲本次教程源碼.你能夠下載後直接在tomcat或其餘web服務器啓動.也能夠自行添加
maven插件啓動.