一、spring Security简介

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


二、建立工程

用第二种方法创建名为spring-security-demo的Maven工程。

工程的最终目录结构为

Spring Security入门Demo_mvc



三、源代码

1 pom.xml里引入所需要的包


  1. <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.0http://maven.apache.org/xsd/maven-4.0.0.xsd">
  2. <modelVersion>4.0.0</modelVersion>
  3. <groupId>spring-security-demo</groupId>
  4. <artifactId>spring-security-demo</artifactId>
  5. <version>0.0.1-SNAPSHOT</version>
  6. <packaging>war</packaging>
  7. <name>spring-security-demo</name>
  8. <description/>
  9. <properties>
  10. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  11. </properties>
  12. <dependencies>
  13. <dependency>
  14. <groupId>javax</groupId>
  15. <artifactId>javaee-api</artifactId>
  16. <version>7.0</version>
  17. <scope>provided</scope>
  18. </dependency>
  19. <dependency>
  20. <groupId>jstl</groupId>
  21. <artifactId>jstl</artifactId>
  22. <version>1.2</version>
  23. </dependency>
  24. <dependency>
  25. <groupId>org.springframework</groupId>
  26. <artifactId>spring-webmvc</artifactId>
  27. <version>3.2.9.RELEASE</version>
  28. <type>jar</type>
  29. <scope>compile</scope>
  30. </dependency>
  31. <dependency>
  32. <groupId>org.springframework</groupId>
  33. <artifactId>spring-context</artifactId>
  34. <version>3.2.9.RELEASE</version>
  35. </dependency>
  36. <dependency>
  37. <groupId>org.springframework.security</groupId>
  38. <artifactId>spring-security-config</artifactId>
  39. <version>3.1.6.RELEASE</version>
  40. <type>jar</type>
  41. <scope>compile</scope>
  42. </dependency>
  43. <dependency>
  44. <groupId>org.springframework.security</groupId>
  45. <artifactId>spring-security-taglibs</artifactId>
  46. <version>3.1.6.RELEASE</version>
  47. <type>jar</type>
  48. <scope>compile</scope>
  49. </dependency>
  50. <dependency>
  51. <groupId>log4j</groupId>
  52. <artifactId>log4j</artifactId>
  53. <version>1.2.15</version>
  54. <type>jar</type>
  55. <scope>compile</scope>
  56. </dependency>
  57. </dependencies>

  58. </project>




  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
  3. <display-name>spring-security-demo</display-name>
  4. <filter>
  5. <filter-name>springSecurityFilterChain</filter-name>
  6. <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  7. </filter>
  8. <filter-mapping>
  9. <filter-name>springSecurityFilterChain</filter-name>
  10. <url-pattern>/*</url-pattern>
  11. </filter-mapping>

  12. <context-param>
  13. <param-name>contextConfigLocation</param-name>
  14. <param-value>
  15. /WEB-INF/spring-security.xml
  16. /WEB-INF/applicationContext.xml
  17. </param-value>
  18. </context-param>

  19. <servlet>
  20. <servlet-name>spring</servlet-name>
  21. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  22. <load-on-startup>1</load-on-startup>
  23. </servlet>
  24. <servlet-mapping>
  25. <servlet-name>spring</servlet-name>
  26. <url-pattern>/</url-pattern>
  27. </servlet-mapping>

  28. <listener>
  29. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  30. </listener>
  31. </web-app>




这里两处关于springsecurity的配置表示项目中所有路径的资源都要经过Spring Security。

注意:最好是将DelegatingFilterProxy写在DispatcherServlet之前,否则Spring Security可能不会正常工作。


3 spring-servlet.xml

​​


  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

  6. <!-- 定义一个视图解析器 -->
  7. <bean id="viewResolver"
  8. class="org.springframework.web.servlet.view.InternalResourceViewResolver"
  9. p:prefix="/WEB-INF/jsp/" p:suffix=".jsp" />
  10. </beans>



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


4 applicationContext.xml


  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:context="http://www.springframework.org/schema/context"
  5. xmlns:mvc="http://www.springframework.org/schema/mvc"
  6. xsi:schemaLocation="http://www.springframework.org/schema/beans
  7. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  8. http://www.springframework.org/schema/context
  9. http://www.springframework.org/schema/context/spring-context-3.0.xsd
  10. http://www.springframework.org/schema/mvc
  11. http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">

  12. <!-- 激活spring的注解. -->
  13. <context:annotation-config />

  14. <!-- 扫描注解组件并且自动的注入spring beans中.
  15. 例如,他会扫描@Controller 和@Service下的文件.所以确保此base-package设置正确. -->
  16. <context:component-scan base-package="com.demo" />

  17. <!-- 配置注解驱动的Spring MVC Controller 的编程模型.注:次标签只在 Servlet MVC工作! -->
  18. <mvc:annotation-driven />

  19. </beans>




5 spring-security.xml


  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:security="http://www.springframework.org/schema/security"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans
  6. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  7. http://www.springframework.org/schema/security
  8. http://www.springframework.org/schema/security/spring-security-3.1.xsd">

  9. <!-- Spring-Security 的配置 -->
  10. <!-- 注意use-expressions=true.表示开启表达式,否则表达式将不可用.
  11. see:http://www.family168.com/tutorial/springsecurity3/html/el-access.html
  12. -->
  13. <security:http auto-config="true" use-expressions="true" access-denied-page="/auth/denied" >

  14. <security:intercept-url pattern="/auth/login" access="permitAll"/>
  15. <security:intercept-url pattern="/main/admin" access="hasRole('ROLE_ADMIN')"/>
  16. <security:intercept-url pattern="/main/common" access="hasRole('ROLE_USER')"/>

  17. <security:form-login
  18. login-page="/auth/login"
  19. authentication-failure-url="/auth/login?error=true"
  20. default-target-url="/main/common"/>

  21. <security:logout
  22. invalidate-session="true"
  23. logout-success-url="/auth/login"
  24. logout-url="/auth/logout"/>

  25. </security:http>

  26. <!-- 指定一个自定义的authentication-manager :customUserDetailsService -->
  27. <security:authentication-manager>
  28. <security:authentication-provider user-service-ref="customUserDetailsService">
  29. <security:password-encoder ref="passwordEncoder"/>
  30. </security:authentication-provider>
  31. </security:authentication-manager>

  32. <!-- 对密码进行MD5编码 -->
  33. <bean class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" id="passwordEncoder"/>

  34. <!--
  35. 通过 customUserDetailsService,Spring会自动的用户的访问级别.
  36. 也可以理解成:以后我们和数据库操作就是通过customUserDetailsService来进行关联.
  37. -->
  38. <bean id="customUserDetailsService" class="com.demo.service.CustomUserDetailsService"/>

  39. </beans>




分析:

(一)

这里/auth/login的权限为permitAll,表示所有人都可以访问此页面;/main/admin的权限为ROLE_ADMIN,表示属于ROLE_ADMIN角色的用户才有权访问此页面;/main/common的权限为ROLE_USER,表示属于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。


(二)


  1. <security:form-login
  2. login-page="/auth/login"
  3. authentication-failure-url="/auth/login?error=true"
  4. default-target-url="/main/common"/>


表示通过 /auth/login这个映射进行登录.


如果验证失败则返回一个URL:/auth/login?error=true 

如果登录成功则默认指向:/main/common


(三)


  1. <security:logout
  2. invalidate-session="true"
  3. logout-success-url="/auth/login"
  4. logout-url="/auth/logout"/>


这里我们开启了session失效功能。注销URL为:/auth/logout;注销成功后转向:/auth/login。



(四)​​​


  1. <bean id="customUserDetailsService" class="com.demo.service.CustomUserDetailsService"/>



一个自定义的CustomUserDetailsService,是实现SpringSecurity的UserDetailsService接口,但我们重写了他即便于我们进行​​数据库​​操作.


<%@ 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>




  1. <%@ page language="java" contentType="text/html; charset=UTF-8"
  2. pageEncoding="UTF-8"%>
  3. <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
  4. <html>
  5. <head>
  6. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  7. <title>Insert title here</title>
  8. </head>
  9. <body>
  10. <h1>Common Page</h1>
  11. <p>每个人都能访问的页面.</p>
  12. <a href="/spring-security-demo/main/admin"> Go AdminPage </a>
  13. <br />
  14. <a href="/spring-security-demo/auth/login">退出登录</a>

  15. </body>
  16. </html>



  1. <%@ page language="java" contentType="text/html; charset=UTF-8"
  2. pageEncoding="UTF-8"%>
  3. <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
  4. <html>
  5. <head>
  6. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  7. <title>Insert title here</title>
  8. </head>
  9. <body>
  10. <h1>Admin Page</h1>
  11. <p>管理员页面</p>
  12. <a href="/spring-security-demo/auth/login">退出登录</a>
  13. </body>
  14. </html>




  1. <%@ page language="java" contentType="text/html; charset=UTF-8"
  2. pageEncoding="UTF-8"%>
  3. <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
  4. <html>
  5. <head>
  6. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  7. <title>Insert title here</title>
  8. </head>
  9. <body>
  10. <h1>你的权限不够!</h1>
  11. <p>只有拥有Admin权限才能访问!</p>
  12. <a href="/spring-security-demo/auth/login">退出登录</a>
  13. </body>
  14. </html>




10 数据模型DbUser.​


  1. package com.demo.domain;

  2. public class DbUser {

  3. private String username;
  4. private String password;
  5. private Integer access;

  6. public String getUsername() {
  7. return username;
  8. }

  9. public void setUsername(String username) {
  10. this.username = username;
  11. }

  12. public String getPassword() {
  13. return password;
  14. }

  15. public void setPassword(String password) {
  16. this.password = password;
  17. }

  18. public Integer getAccess() {
  19. return access;
  20. }

  21. public void setAccess(Integer access) {
  22. this.access = access;
  23. }

  24. }




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


  1. package com.demo.dao;

  2. import java.util.ArrayList;
  3. import java.util.List;

  4. import org.apache.log4j.Logger;
  5. import com.demo.domain.DbUser;

  6. public class UserDao {

  7. protected static Logger logger = Logger.getLogger("dao");

  8. public DbUser getDatabase(String username) {

  9. List<DbUser> users = internalDatabase();

  10. for (DbUser dbUser : users) {
  11. if (dbUser.getUsername().equals(username) == true) {
  12. logger.debug("User found");
  13. return dbUser;
  14. }
  15. }
  16. logger.error("User does not exist!");
  17. throw new RuntimeException("User does not exist!");

  18. }

  19. /**
  20. * 初始化数据
  21. */
  22. private List<DbUser> internalDatabase() {

  23. List<DbUser> users = new ArrayList<DbUser>();
  24. DbUser user = null;

  25. user = new DbUser();
  26. user.setUsername("admin");

  27. // "admin"经过MD5加密后
  28. user.setPassword("21232f297a57a5a743894a0e4a801fc3");
  29. user.setAccess(1);

  30. users.add(user);

  31. user = new DbUser();
  32. user.setUsername("user");

  33. // "user"经过MD5加密后
  34. user.setPassword("ee11cbb19052e40b07aac0ca060c23ee");
  35. user.setAccess(2);

  36. users.add(user);

  37. return users;

  38. }
  39. }




12 CustomUserDetailsService.java,自定义UserDetailsService,可以通过继承UserDetailsService来达到灵活的自定义UserDetailsService


  1. package com.demo.service;

  2. import java.util.ArrayList;
  3. import java.util.Collection;
  4. import java.util.List;

  5. import org.apache.log4j.Logger;
  6. import com.demo.dao.UserDao;
  7. import com.demo.domain.DbUser;
  8. import org.springframework.dao.DataAccessException;
  9. import org.springframework.security.core.GrantedAuthority;
  10. import org.springframework.security.core.authority.GrantedAuthorityImpl;
  11. import org.springframework.security.core.userdetails.User;
  12. import org.springframework.security.core.userdetails.UserDetails;
  13. import org.springframework.security.core.userdetails.UserDetailsService;
  14. import org.springframework.security.core.userdetails.UsernameNotFoundException;

  15. /**
  16. * 一个自定义的service用来和数据库进行操作. 即以后我们要通过数据库保存权限.则需要我们继承UserDetailsService
  17. *
  18. */
  19. public class CustomUserDetailsService implements UserDetailsService {

  20. protected static Logger logger = Logger.getLogger("service");

  21. private UserDao userDAO = new UserDao();

  22. public UserDetails loadUserByUsername(String username)
  23. throws UsernameNotFoundException, DataAccessException {

  24. UserDetails user = null;

  25. try {

  26. // 搜索数据库以匹配用户登录名.
  27. // 我们可以通过dao使用JDBC来访问数据库
  28. DbUser dbUser = userDAO.getDatabase(username);

  29. // Populate the Spring User object with details from the dbUser
  30. // Here we just pass the username, password, and access level
  31. // getAuthorities() will translate the access level to the correct
  32. // role type

  33. user = new User(dbUser.getUsername(), dbUser.getPassword()
  34. .toLowerCase(), true, true, true, true,
  35. getAuthorities(dbUser.getAccess()));

  36. } catch (Exception e) {
  37. logger.error("Error in retrieving user");
  38. throw new UsernameNotFoundException("Error in retrieving user");
  39. }

  40. return user;
  41. }

  42. /**
  43. * 获得访问角色权限
  44. *
  45. * @param access
  46. * @return
  47. */
  48. public Collection<GrantedAuthority> getAuthorities(Integer access) {

  49. List<GrantedAuthority> authList = new ArrayList<GrantedAuthority>(2);

  50. // 所有的用户默认拥有ROLE_USER权限
  51. logger.debug("Grant ROLE_USER to this user");
  52. authList.add(new GrantedAuthorityImpl("ROLE_USER"));

  53. // 如果参数access为1.则拥有ROLE_ADMIN权限
  54. if (access.compareTo(1) == 0) {
  55. logger.debug("Grant ROLE_ADMIN to this user");
  56. authList.add(new GrantedAuthorityImpl("ROLE_ADMIN"));
  57. }

  58. return authList;
  59. }
  60. }




13 控制器LoginController.java


  1. package com.demo.controller;

  2. import org.apache.log4j.Logger;
  3. import org.springframework.stereotype.Controller;
  4. import org.springframework.ui.ModelMap;
  5. import org.springframework.web.bind.annotation.RequestMapping;
  6. import org.springframework.web.bind.annotation.RequestMethod;
  7. import org.springframework.web.bind.annotation.RequestParam;

  8. @Controller
  9. @RequestMapping("auth")
  10. public class LoginController {
  11. protected static Logger logger = Logger.getLogger("controller");

  12. /**
  13. * 指向登录页面
  14. */
  15. @RequestMapping(value = "/login", method = RequestMethod.GET)
  16. public String getLoginPage(
  17. @RequestParam(value = "error", required = false) boolean error,
  18. ModelMap model) {

  19. logger.debug("Received request to show login page");

  20. if (error == true) {
  21. // Assign an error message
  22. model.put("error",
  23. "You have entered an invalid username or password!");
  24. } else {
  25. model.put("error", "");
  26. }
  27. return "loginpage";
  28. }

  29. /**
  30. * 指定无访问额权限页面
  31. *
  32. * @return
  33. */
  34. @RequestMapping(value = "/denied", method = RequestMethod.GET)
  35. public String getDeniedPage() {

  36. logger.debug("Received request to show denied page");

  37. return "deniedpage";
  38. }
  39. }



该controller有两个mapping映射

main/common

main/admin

现在我们将同过Spring Security框架实现成功登陆的人都能访问到main/common,但只有拥有admin权限的用户才能访问main/admin。


14 控制器MainController.java


  1. package com.demo.controller;

  2. import org.apache.log4j.Logger;
  3. import org.springframework.stereotype.Controller;
  4. import org.springframework.web.bind.annotation.RequestMapping;
  5. import org.springframework.web.bind.annotation.RequestMethod;

  6. @Controller
  7. @RequestMapping("/main")
  8. public class MainController {
  9. protected static Logger logger = Logger.getLogger("controller");

  10. /**
  11. * 跳转到commonpage页面
  12. *
  13. * @return
  14. */
  15. @RequestMapping(value = "/common", method = RequestMethod.GET)
  16. public String getCommonPage() {
  17. logger.debug("Received request to show common page");
  18. return "commonpage";
  19. }

  20. /**
  21. * 跳转到adminpage页面
  22. *
  23. * @return
  24. */
  25. @RequestMapping(value = "/admin", method = RequestMethod.GET)
  26. public String getAadminPage() {
  27. logger.debug("Received request to show admin page");
  28. return "adminpage";

  29. }

  30. }




四、运行结果

1 启动spring-security-demo程序

Spring Security入门Demo_spring_02


2 在浏览器里输入​​http://localhost:8080/spring-security-demo/auth/login​

Spring Security入门Demo_java_03


3 输入用户名admin密码admin后,点击“Login”按纽

Spring Security入门Demo_html_04


Spring Security入门Demo_html_05


4 点击“​​Go​​ AdminPage”链接,因为有权限,所以可看到管理员页面

Spring Security入门Demo_html_06


5 点击“退出登录”,返回登录页

Spring Security入门Demo_xml_07


6 输入用户名user密码user并登录

Spring Security入门Demo_spring_08


Spring Security入门Demo_java_09


7 点击Go AdminPage链接,因为没有权限,所以看到权限不够的提示

Spring Security入门Demo_xml_10


8 退出登录,返回登录页

Spring Security入门Demo_mvc_11