Spring Security3 - MVC 整合教程 (初識Spring Security3)(轉)

原文地址:http://liukai.iteye.com/blog/982088html

下面咱們將實現關於Spring Security3的一系列教程. 
最終的目標是整合Spring Security + Spring3MVC 
完成相似於SpringSide3中mini-web的功能. 

Spring Security是什麼? 

引用
Spring Security,這是一種基於Spring AOP和Servlet過濾器的安全框架。它提供全面的安全性解決方案,同時在Web請求級和方法調用級處理身份確認和受權。在Spring Framework基礎上,Spring Security充分利用了依賴注入(DI,Dependency Injection)和麪向切面技術。


關於Spring Security學習的資料. 
最重要,最齊全的中文資料固然是family168的中文文檔 
Spring Security2參考文檔 

Spring Security3 參考文檔 

附件包含了一個很好的初入門的PDF教程. 
最好是花30分鐘先照着PDF上的教程一步一步的操做. 
雖然沒有實際的應用價值,但對初學者認識SpringSecurity3頗有幫助. 

咱們的項目目錄結構最終是: 

 


須要添加的jar包: 

 


咱們先實現一個controller: 

MainController.java 
 
package org.liukai.tutorial.controller;

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

@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. 
<?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的配置 

<?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插件啓動. 
相關文章
相關標籤/搜索