几乎只要是系统永远都存在有一个功能--登录与权限分配;而Spring考虑到了这种情况,专门提供了一个用于登录验证以及单Session验证,角色分配的框架概念--Spring安全框架
开发准备:
本次将采用Spring MVC的结构进行安全框架的开发.
新建一个项目:SpringSecurityProject
Spring安全框架的设计理念在于:你可以直接在开发完成的项目上配置安全框架.
范例:在web.xml里面配置Spring的相关信息
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter
</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
2.修改applicationContext.xml文件定义springMVC相关配置信息;
<mvc:annotation-driven/>
<mvc:default-servlet-handler/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<array>
<value>Message</value>
<value>Pages</value>
</array>
</property>
</bean>
此时所有的相关页面都要求在WEB-INF目录下保存.
3.定义Message.java文件.
package cn.zwb.vo;
import java.io.Serializable;
@SuppressWarnings("serial")
public class Message implements Serializable{
private Integer mid;
private String title;
private String content;
public Integer getMid() {
return mid;
}
public void setMid(Integer mid) {
this.mid = mid;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
4.定义MessageAction.java程序类
package cn.zwb.action;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import cn.zwb.vo.Message;
@Controller
@RequestMapping("/pages/back/message/*")
public class MessageAction {
@RequestMapping("message_insertpre")
public ModelAndView insertPre(){
ModelAndView mav=new ModelAndView("back/message/message_insert");
return mav;
}
@RequestMapping("message_insert")
public ModelAndView insert(Message msg){
System.out.println("[添加数据]"+msg);
ModelAndView mav =new ModelAndView("forward");
mav.addObject("msg","信息添加成功!");
mav.addObject("url","/pages/back/message/message_insertpre.action");
return mav;
}
@RequestMapping("message_list")
public ModelAndView list(){
ModelAndView mav=new ModelAndView("/back/message/message_list");
List<Message> all=new ArrayList<Message>();
for (int i = 0; i < 10; i++) {
Message msg=new Message();
msg.setMid(i);
msg.setTitle("mldn-"+i);
msg.setContent("www.baidu.com");
all.add(msg);
}
return mav;
}
@RequestMapping("message_delete")
public ModelAndView delete (Integer mid){
System.out.println("[删除数据]数据编号为:"+mid);
ModelAndView mav =new ModelAndView("forward");
mav.addObject("msg","信息添加成功!");
mav.addObject("url","/pages/back/message/message_list.action");
return mav;
}
}
5.准备出相关的几个界面
增加数据页面
<body>
<form action="pages/back/message/message_insert.action">
消息编号:<input type="text" name="mid" id="mid"><br>
新闻标题:<input type="text" name="title" id="title"><br>
新闻内容:<input type="text" name="content" id="content">
<input type="submit" value="发布">
<input type="reset" value="重置">
</form>
</body>
显示与删除
<%@page import="java.util.Iterator"%>
<%@page import="java.util.List"%>
<%@ page language="java" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">
<title>My JSF 'MyJsp.jsp' starting page</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
<!--
<link rel="stylesheet" type="text/css" href="styles.css">
-->
</head>
<body>
<table border="1" width="100%">
<tr>
<td>消息编号</td>
<td>消息名称</td>
<td>消息内容</td>
<td>删除</td>
</tr>
<c:forEach var="message" items="${allMessages}">
<tr>
<td>${message.mid}</td>
<td>${message.title}</td>
<td>${message.content}</td>
<td><a href="pages/back/message/message_delete.action?mid=${message.mid }">删除消息</td>
</tr>
</c:forEach>
</table>
</body>
</html>
现在假设几种情况,列表可以所有人看,但是删除只能够由固定的人删除
固定信息验证
一般情况下如果要进行登陆验证往往都会利用数据库,但是考虑到数据库的开发有一些要求,随意本处先使用固定的用户名和密码进行验证,为了方便现在准备出两个用户:
管理员用户:admin/hello: ROLE_ADMIN,ROLE_USER;
普通用户:zwb/java:ROLE_USER
只要是进行登录的检验,几乎都是依靠这过滤器完成的,所以必须在项目的web.xml文件里面配置一个专门的验证过滤器:org.springframework.web.filter.DelegatingFilterProxy
范例:在web.xml文件里面定义授权验证器
这个过滤器的名字必须是springSecurityFilterChain
<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>
此处只是启动了一个验证的过滤器,但是具体要验证哪些内容还没有定义.
范例:在applicationContextxml文件里面配置用户相关角色
导入安全验证的命名空间
xmlns:security="http://www.springframework.org/schema/security"
定义Spring的安全框架的相关配置:
<!-- 启动Spring的安全验证 -->
<security:global-method-security jsr250-annotations="enabled" secured-annotations="enabled"/>
<!-- 使用默认的安全配置操作,如果出现了授权访问错误,则跳转到403.jsp -->
<security:http auto-config="true" access-denied-page="/403.jsp"/>
<!-- 配置授权管理器,以后即便使用了数据库的验证,此处也要配置授权管理器 -->
<security:authentication-manager alias="authenticationManager">
<!-- 定义用户的基本权限信息,本处使用的是固定信息 -->
<security:authentication-provider>
<!-- 创建固定的用户名与密码验证 -->
<security:user-service>
<security:user name="admin" password="123456" authorities="ROLE_ADMIN,ROLE_USER"/>
<security:user name="zwb" password="java" authorities="ROLE_USER"/>
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
此时准备处理相关的用户以及安全框架的自动配置,但是所有的配置最终生效还需要在Action里面修改
范例:修改MessageAction程序类,增加相关的权限配置
package cn.zwb.action;
import java.util.ArrayList;
import java.util.List;
import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import cn.zwb.vo.Message;
@Controller
@RequestMapping("/pages/back/message/*")
public class MessageAction {
@RequestMapping("message_insertpre")
@Secured(value={"ROLE_ADMIN"})
public ModelAndView insertPre(){
ModelAndView mav=new ModelAndView("message/message_insert");
return mav;
}
@RequestMapping("message_insert")
@Secured(value={"ROLE_ADMIN"})
public ModelAndView insert(Message msg){
System.out.println("[添加数据]"+msg);
ModelAndView mav =new ModelAndView("forward");
mav.addObject("msg","信息添加成功!");
mav.addObject("url","pages/back/message/message_insertpre.action");
return mav;
}
@RequestMapping("message_list")
@Secured(value={"ROLE_ADMIN","ROLE_USER"})
public ModelAndView list(){
ModelAndView mav=new ModelAndView("message/message_list");
List<Message> all=new ArrayList<Message>();
for (int i = 0; i < 10; i++) {
Message msg=new Message();
msg.setMid(i);
msg.setTitle("mldn-"+i);
msg.setContent("www.baidu.com");
all.add(msg);
}
mav.addObject("allMessages",all);
return mav;
}
@RequestMapping("message_delete")
@Secured(value={"ROLE_ADMIN"})
public ModelAndView delete (Integer mid){
System.out.println("[删除数据]数据编号为:"+mid);
ModelAndView mav =new ModelAndView("forward");
mav.addObject("msg","信息删除成功!");
mav.addObject("url","/pages/back/message/message_list.action");
return mav;
}
}
随后在进行调用过程之中,会自动根据用户的角色来进行操作访问.
在配置成功之后,如果用户没有登录的前提下,那么直接访问授权路径时就会自动跳转到一个登录页面,而此时的路径名称:spring_security_login.包括这个程序的页面都是由Spring自己提供的.登录的检查页面j_spring_security_check,登录的注销页面:j_spring_security_logout.
现在经过一系列的验证之后,的确可以发现,Spring的安全框架在登录的处理上的确很方便.但是使用的过程里面会存在有一些问题
很多时候可能需要session中保存的这个用户id信息;
既然删除操作只有管理可以进行,那么如果是普通用户不应该出现删除的连接
范例:在Action中取出用户名
用户信息:org.springframework.security.core.userdetails.UserDetails
@RequestMapping("message_list")
@Secured(value={"ROLE_ADMIN","ROLE_USER"})
public ModelAndView list(){
ModelAndView mav=new ModelAndView("message/message_list");
UserDetails userdetails=(UserDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
System.out.println("[当前用户]:"+userdetails.getUsername());
System.out.println("[登录密码]:"+userdetails.getPassword());
System.out.println("[用户角色]:"+userdetails.getAuthorities());
List<Message> all=new ArrayList<Message>();
for (int i = 0; i < 10; i++) {
Message msg=new Message();
msg.setMid(i);
msg.setTitle("mldn-"+i);
msg.setContent("www.baidu.com");
all.add(msg);
}
mav.addObject("allMessages",all);
return mav;
}
当访问需要登录之后的页面,没有登录的话会跳转到登录页面,登录之后会重新跳转的要操作的页面
范例:在JSP上取得用户名
<h1>欢迎:${sessionScope['SPRING_SECURITY_CONTEXT'].authentication.principal.username}登录</h1>
以上只是取得了用户登录的基本信息,而实际上也可以在页面上取得相应的角色信息,但是这个时候就需要使用到Spring特定的标签完成了
范例:在页面中输出角色信息
定义要使用的安全标签:
<%@taglib prefix="security" uri="http://www.springframework.org/security/tags" %>
输出所有的角色信息:
<h2>
<security:authentication property="authorities" var="aut"/>
${aut}
</h2>
所有的角色都会以集合的形式返回.
对于删除操作:是需要授权验证的,如果是管理员可以进行删除,如果是普通用户不应该出现删除
<security:authorize ifAnyGranted="ROLE_ADMIN">
<td>删除</td>
</security:authorize>
<security:authorize ifAnyGranted="ROLE_ADMIN">
<td><a href="pages/back/message/message_delete.action?mid=${message.mid }">删除消息</td>
</security:authorize>
整个Spring的安全框架虽然简单,虽然感觉上方便,但是自动配置的集成度有点太高了
扩展注销功能
现在已经成功实现Spring自己管理的登录操作包括注销的实现,但是很多时候对于前台页面的要求是很高的,
你不能说直接给一个素颜的页面.往往需要在项目里面定义属于自己的漂流的登录页面,
范例:定义一个专门的登录页
<body>
<form action="<%=basePath %>login" method="post">
用户名:<input type="text" name="j_username" id="j_username"><br>
密 码:<input type="password" name="j_password" id="j_password"><br>
<input type="submit" value="登录">
</form>
</body>
随后需要手工的来进行登录的处理操作.
范例:修改applicationContext.xml文件使用手工配置
<security:http access-denied-page="/403.jsp">
<security:form-login
login-page="/login.jsp"
login-processing-url="/login"
default-target-url="/pages/back/message/message_list.action"
authentication-failure-url="/login?error=true"/>
<security:logout logout-success-url="/login.jsp"
delete-cookies="JSESSIONID"
/>
<security:http access-denied-page="/403.jsp">
<security:form-login
login-page="/login.jsp" 登录页面的路径
login-processing-url="/login" 登录页面的提交
default-target-url="/pages/back/message/message_list.action" 首页
authentication-failure-url="/login?error=true"/>当登录出错后跳转的页面
<security:logout logout-success-url="/login.jsp" 注销后跳转的页面
delete-cookies="JSESSIONID" 注销后清除的COOKIE
/>
范例:在message_jsp中增加注销连接
<a href="<%=basePath %>j_spring_security_logout">注销</a>
如果登录错误,则直接在login.jsp页面上判断是否存在有error参数以确定提示信息.
<h1>${param.error==true?"登录失败,错误的用户名或密码":""}</h1>
此时可以由用户自己来决定登录页面.
Session管理
在一个登录系统之中必须有一个前提:一个账户只能够由一个人登录,那么在Spring安全框架之中就提供有此类的验证方式.也就是说利用一些配置就可以针对于当前Session进行管理
范例:在Web.xml文件里面配置监听器
org.springframework.security.web.session.HttpSessionEventPublisher
<listener>
<listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
</listener>
此时用户登录之后就会对用户的信息进行保存,而且会保存在application属性之中,如果一旦出现了重复,那么就会通知之前的session注销.
范例:applicationContext文件里面设置Session管理
<!-- 配置Session的管理操作,如果Session失效跳转到login.jsp上去 -->
<security:session-management invalid-session-url="/login.jsp?ses=invalid">
<security:concurrency-control
max-sessions="1"
error-if-maximum-exceeded="false"
expired-url="/login.jsp?session=destory"/>
</security:session-management>
如果现在用户出现了重复登录(最大的session保存只能够是1,就表示每个session只能保存一次).会跳转到login.jsp页面上.
记住密码
<>================
DWQ