• Spring Security3学习实例


    Spring Security是什么?

    Spring Security,这是一种基于Spring AOP和Servlet过滤器的安全框架。它提供全面的安全性解决方案,同时在Web请求级和方法调用级处理身份确认和授权。在Spring Framework基础上,Spring Security充分利用了依赖注入(DI,Dependency Injection)和面向切面技术。

    下面开始通过一个简单实例来理解security是如何控制权限的

    注:

    实例中将同过Spring Security3框架实现成功登陆的人都能访问到main/common. 
    但只有拥有admin权限的用户才能访问main/admin. 

    比实列使用maven工具

    新建一个maven-web工程(新建步骤maven搭建maven web工程

    maven的pom.xml导入的包

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>com.sys</groupId>
        <artifactId>SpringSecurity3</artifactId>
        <packaging>war</packaging>
        <version>0.0.1-SNAPSHOT</version>
        <name>SpringSecurity3 Maven Webapp</name>
        <url>http://maven.apache.org</url>
        <dependencies>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.8.1</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc</artifactId>
                <version>3.0.5.RELEASE</version>
                <type>jar</type>
                <scope>compile</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-orm</artifactId>
                <version>3.0.5.RELEASE</version>
                <type>jar</type>
                <scope>compile</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.security</groupId>
                <artifactId>spring-security-config</artifactId>
                <version>3.0.5.RELEASE</version>
                <type>jar</type>
                <scope>compile</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.security</groupId>
                <artifactId>spring-security-taglibs</artifactId>
                <version>3.0.5.RELEASE</version>
                <type>jar</type>
                <scope>compile</scope>
            </dependency>
            <dependency>
                <groupId>taglibs</groupId>
                <artifactId>standard</artifactId>
                <version>1.1.2</version>
                <type>jar</type>
                <scope>compile</scope>
            </dependency>
            <dependency>
                <groupId>jstl</groupId>
                <artifactId>jstl</artifactId>
                <version>1.1.2</version>
                <scope>compile</scope>
            </dependency>
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>1.2.16</version>
            </dependency>
        </dependencies>
        <build>
            <finalName>SpringSecurity3</finalName>
        </build>
    </project>

    包结构图

    首先提供一个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中对外提供两个访问地址

    main/common 
    main/admin

    跟上我们的要求,拥有权限的人可以访问admin,其他普通用户只能访问common

    接下来开启控制

    web.xml中开启Spring3MVC和SpringSecurity3.

    <?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 -->
        <!-- 注意一点.最好是将DelegatingFilterProxy写在DispatcherServlet之前.否则SpringSecurity可能不会正常工作.  -->
        <filter>
            <filter-name>springSecurityFilterChain</filter-name>
            <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        </filter>
        <!-- 过滤所有url都要安全处理 ,表示项目中所有路径的资源都要经过SpringSecurity.  -->
        <filter-mapping>
            <filter-name>springSecurityFilterChain</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
        <!-- 指定的SpringSecurity配置 :spring-security.xml  -->
        <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>
          <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>WEB-INF/spring-servle.xml</param-value>
          </init-param>
            <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>  

    配置spring的文件

    spring-servle.xml:配置声明一个视图解析器.在控制器中会根据JSP名映射到/ WEB-INF/jsp中相应的位置.

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

    applicationContext.xml:spring的注入机制等

    <?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="com.*"/>  
      
        <!-- 配置注解驱动的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="/SpringSecurity3/main/admin"> Go AdminPage </a>  
        <br />  
        <a href="/SpringSecurity3/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="/SpringSecurity3/auth/login">退出登录</a>  
    </body>  
    </html>  

    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页面..

    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.表示开启表达式.     -->
        <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

    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/SpringSecurity3/auth/login

    本文参考来自这里

  • 相关阅读:
    2.1.7出现异常,锁自动释放
    2.1.5脏读
    2.1.4synchronized方法与锁对象
    2.1.3多个对象多个锁
    2.1.2实例变量非线程安全
    2.1.1方法内的变量为线程安全
    Linux内核开发
    fl2440 platform总线led字符设备驱动
    fl2440字符设备led驱动
    cdev结构体及其相关函数
  • 原文地址:https://www.cnblogs.com/hwaggLee/p/4628639.html
Copyright © 2020-2023  润新知