下面咱们将实现关于Spring Security3的一系列教程.
最终的目标是整合Spring Security + Spring3MVC
完成相似于SpringSide3中mini-web的功能.
Spring Security是什么?
html
引用java
Spring Security,这是一种基于Spring AOP和Servlet过滤器的安全框架。它提供全面的安全性解决方案,同时在Web请求级和方法调用级处理身份确认和受权。在Spring Framework基础上,Spring Security充分利用了依赖注入(DI,Dependency Injection)和面向切面技术。web
关于Spring Security学习的资料.
最重要,最齐全的中文资料固然是family168的中文文档
Spring Security2参考文档
Spring Security3 参考文档
附件包含了一个很好的初入门的PDF教程.
最好是花30分钟先照着PDF上的教程一步一步的操做.
虽然没有实际的应用价值,但对初学者认识SpringSecurity3颇有帮助.
咱们的项目目录结构最终是:
须要添加的jar包:
咱们先实现一个controller:
MainController.java
spring
-
package org.liukai.tutorial.controller; express
-
-
import org.apache.log4j.Logger; apache
-
import org.springframework.stereotype.Controller; 编程
-
import org.springframework.web.bind.annotation.RequestMapping; spring-mvc
-
import org.springframework.web.bind.annotation.RequestMethod; tomcat
-
-
@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.
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的配置
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插件启动.