您的位置:首页 > 编程语言 > Java开发

Spring Security3 - MVC 整合教程 (初识Spring Security3)

2016-12-05 15:24 453 查看
下面我们将实现关于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

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


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

Xml代码  


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


<?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代码  


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

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

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

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

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


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


<?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对应的三个权限

Xml代码  


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

所以

Xml代码  


<security:intercept-url pattern="/auth/login" access="permitAll"/>  

表示所有的人都可以访问/auth/login.

Xml代码  


<security:intercept-url pattern="/main/admin" access="hasRole('ROLE_ADMIN')"/>  
        <security:intercept-url pattern="/main/common" access="hasRole('ROLE_USER')"/>  

则表示只有拥有对应的角色才能访问.

Xml代码  


<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

Xml代码  


security:logout   
                invalidate-session="true"   
                logout-success-url="/auth/login"   
                logout-url="/auth/logout"/>  

很简单.我们开启了session失效功能.

注销URL为:/auth/logout

注销成功后转向:/auth/login

Xml代码  


<!-- 指定一个自定义的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

Java代码  


package org.liukai.tutorial.domain;  
  
public class DbUser {  
  
    private String username;  
    private String password;  
    private Integer access;  
  
     //getter/setter  
  
}  

通过一个初始化的List来模拟数据库操作.

UserDao.java

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

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插件启动.

SpringSecurity3.0.pdf (1.1 MB)
下载次数: 7720
spring3-security-integration.zip (25 KB)
下载次数: 5233
查看图片附件
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: