zoukankan      html  css  js  c++  java
  • Spring Security3

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

    下面我们将实现关于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插件启动. 
    在追随技术的道路上,十年如一日~
  • 相关阅读:
    PAT 1010. 一元多项式求导 (25)
    PAT 1009. 说反话 (20) JAVA
    PAT 1009. 说反话 (20)
    PAT 1007. 素数对猜想 (20)
    POJ 2752 Seek the Name, Seek the Fame KMP
    POJ 2406 Power Strings KMP
    ZOJ3811 Untrusted Patrol
    Codeforces Round #265 (Div. 2) 题解
    Topcoder SRM632 DIV2 解题报告
    Topcoder SRM631 DIV2 解题报告
  • 原文地址:https://www.cnblogs.com/iamcui/p/4441911.html
Copyright © 2011-2022 走看看