非常感谢https://blog.csdn.net/lee353086/article/details/52610205
SpringSecurity技术学习,更多知识请访问https://www.itkc8.com
环境
[1]Spring 3.1.2
[2]Tomcat 7.0.68
概要
完美的解决了《学习三》中自定义login方法得绕过Spring Security部份class的缺陷。
最新的例子,解决以下问题
[1]如果login递交的数据,要求验证“验证码”怎么办。
[2]若访问页面没有权限,返回json形式的错误提示。
[3]若login递交的数据非法,返回json形式的错误提示。
[4]若login递交的数据合法,返回json形式的提示。
[5]同个帐号只能同时一次有效,若异地已经登录了这个帐号,会自动把它踢掉。
[6]如何查看登录到Web App的所有用户信息。
也能解决
[1]若已经在其它地方登录的帐户,可以提个醒,你已经把其它地方登录的这个帐号踢掉了。
通过修改MyAuthenticationFilter.java。
[2]若你已经在其它地方登录,不允许再登录。
通过修改spring-security.xml。
正文中会介绍下主要类的功能,然后直接上代码。
《学习二》中未动的代码,这里不重复贴了。
本文的例子在Chrome和Firefox下测试通过。
理解整个Demo建议从spring-security.xml文件开始。
正文
相对于《学习二》这里最重要的是五个java文件,一个配置文件,必须要深刻理解它们之间的关系和功能。
MyAuthenticationEntryPoint.java
当用户没有权限访问某个资源的时候,你可以在这里自定义返回内容。
MyAuthenticationFilter.java
自定义login请求的格式,比如你想上传json格式的请求,可以在这里处理。
并验证用户的请求是否合法,如果不合法你可以抛出继承自AuthenticationException的Exception
MyAuthenticationException.java
继承AuthenticationException,在MyAuthenticationFilter中抛出后,交给MyAuthenticationFailureHandler处理
MyAuthenticationFailureHandler.java
当login失败,这里可以自定义返回的错误信息。
MyAuthenticationSuccessHandler.java
如何登录成功,这里可以自定义返回的成功信息。
配置文件
web.xml
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="schedule-console" version="3.0">
spring-security.xml
xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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.1.xsd">
entry-point-ref="authenticationEntryPoint">
logout-success-url="/main/customLogin.do"
invalidate-session="true" />
class="org.springframework.security.web.session.ConcurrentSessionFilter">
class="com.nuoke.MyAuthenticationFilter">
class="com.nuoke.MyFilterSecurityInterceptor">
class="com.nuoke.MyAccessDecisionManager">
class="org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy">
class="com.nuoke.MyInvocationSecurityMetadataSource" />
java文件
MyAuthenticationEntryPoint.java
package com.nuoke;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
public class MyAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint{
//当访问的资源没有权限,会调用这里
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
//super.commence(request, response, authException);
//返回json形式的错误信息
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
response.getWriter().println("{\"ok\":0,\"msg\":\""+authException.getLocalizedMessage()+"\"}");
response.getWriter().flush();
}
}
MyAuthenticationException.java
package com.nuoke;
import org.springframework.security.core.AuthenticationException;
public class MyAuthenticationException extends AuthenticationException {
/**
*
*/
private static final long serialVersionUID = 1L;
public MyAuthenticationException(String msg) {
super(msg);
// TODO Auto-generated constructor stub
}
}
MyAuthenticationFailureHandler.java
package com.nuoke;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.WebAttributes;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
public class MyAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler{
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
// Example1(request,response,exception);
// Example2(request,response,exception);
Example3(request,response,exception);
}
private void Example1(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException
{
//例1:直接返回字符串
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
response.getWriter().println("{\"ok\":0,\"msg\":\""+exception.getLocalizedMessage()+"\"}");
}
private void Example2(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException
{
String strUrl = request.getContextPath() + "/customLoginResponse.jsp";
request.getSession().setAttribute("ok", 0);
request.getSession().setAttribute("message", exception.getLocalizedMessage());
request.getSession().setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);
super.onAuthenticationFailure(request, response, exception);
}
private void Example3(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException
{
//例3:自定义跳转到哪个URL
//假设login.jsp在webapp路径下
//注意:不能访问WEB-INF下的jsp。
String strUrl = request.getContextPath() + "/customLoginResponse.jsp";
request.getSession().setAttribute("ok", 0);
request.getSession().setAttribute("message", exception.getLocalizedMessage());
request.getSession().setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);
//Error request.getRequestDispatcher(strUrl).forward(request, response);
response.sendRedirect(strUrl);
}
}
MyAuthenticationFilter.java
package com.nuoke;
import java.util.Enumeration;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/*
* 说明:
* UsernamePasswordAuthenticationFilter用于处理来自表单提交的认证。该表单必须提供对应的用户名和密码,
* 对应的参数名默认为j_username和j_password。
* 如果不想使用默认的参数名,可以通过UsernamePasswordAuthenticationFilter的usernameParameter和passwordParameter进行指定。
* 表单的提交路径默认是“j_spring_security_check”,可以通过UsernamePasswordAuthenticationFilter的filterProcessesUrl进行指定。
* 通过属性postOnly可以指定只允许登录表单进行post请求,默认是true。
*/
public class MyAuthenticationFilter extends UsernamePasswordAuthenticationFilter{
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
//这里可以抛出继承自AuthenticationException的exception
//然后会转到MyAuthenticationFailureHandler。
//比如说验证码什么的可以在这里验证,然后抛出异常。
//然后让MyAuthenticationFailureHandler去处理,并输出返回
//下面的代码段是具体的示例
//当用户输入的用户名为“123”抛出自定义的AuthenticationException异常。
String username = request.getParameter("username");
if(username.equals("123"))
{
throw new MyAuthenticationException("测试异常被MyAuthenticationFailureHandler处理");
}
return super.attemptAuthentication(request, response);
}
}
MyAuthenticationSuccessHandler.java
package com.nuoke;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler{
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws ServletException, IOException
{
//例1:不跳到XML设定的页面,而是直接返回json字符串
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
response.getWriter().println("{\"ok\":\"1\",\"msg\":\"登录成功\"}");
//例2:跳转到XML中设定的URL。其实已经没有定义这个class的意义
//super.onAuthenticationSuccess(request, response, authentication);
//例3:自定义跳转到哪个URL
//http://cl315917525.iteye.com/blog/1768396
}
}
MyController.java
package com.nuoke.controller;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
@Controller
@RequestMapping(value = "/main")
public class MyController {
@Autowired
@Qualifier("sessionRegistry")
private SessionRegistry sessionRegistry;
@RequestMapping(value = "/admin.do")
public ModelAndView adminPage() {
ModelAndView model = new ModelAndView();
model.addObject("title", "Spring Security Hello World");
model.addObject("message", "这是一个安全被保护的页面!");
//在MyInvocationSecurityMetadataSource类中指定了保护。
model.setViewName("admin");
return model;
}
@RequestMapping(value = "/welcome.do")
public ModelAndView WelcomeAction() {
this.PrintAllOnlineUser();
ModelAndView model = new ModelAndView();
model.addObject("title", "Spring Security Hello World");
model.addObject("message", "这是一个欢迎页面!");
model.setViewName("welcome");
return model;
}
//打印在线用户
void PrintAllOnlineUser()
{
List
List
for (Object principal: principals) {
if (principal instanceof User) {
usersNamesList.add(((User) principal).getUsername());
}
}
System.out.println("count:"+usersNamesList.size()+"=>"+usersNamesList.toString());
}
@RequestMapping(value="/customLogin.do")
public String customLoginAction(HttpServletRequest request){
return "customLogin";
}
}//end class
jsp文件
webapp目录下的
customLoginResponse.jsp
<%@ page language="java" import="java.util.*,java.text.*" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
{"ok":${sessionScope.ok},"msg":"${sessionScope.message}","SPRING_SECURITY_LAST_EXCEPTION":"${sessionScope.SPRING_SECURITY_LAST_EXCEPTION}"}
MyException.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
Exception ex = (Exception) request.getAttribute("Exception");
String strMsg = "未知错误";
if(ex!=null)
strMsg = ex.getMessage();
%>
{"ok":"0","msg":"<%=strMsg%>"}
sessionExpired.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
{"ok":0,"msg":"你已经在其它地方登录!"}
My404.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
{"ok":0,"msg":"404错误"}
WEB-INF/view目录下的
customLogin.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
src="../common/bootstrap/js/bootstrap.min.js">
src="../common/jquery/jquery-2.1.1.min.js">
示例二 自定义login方法
$(function(){
/////////////////登录提交////////////////////////////
$("#loginForm").submit(function() {
var username=$("#username").val();
var password=$("#password").val();
var data={username:username,password:password};
var url="/testSpringSecurity2/main/customLogin2.do";
$.ajax({
type: "POST",
url: url,
data: data,
// contentType: "application/json",
dataType: "json",
success:function (result) {
if(result.ok){
location.href="/testSpringSecurity2/main/admin.do";
} else
{
alert(">>"+result.SPRING_SECURITY_LAST_EXCEPTION)
$(".error").remove();
$("#loginForm").prepend("
}
},
error:function(XMLHttpRequest, textStatus, errorThrown){
alert('读取超时,请检查网络连接...');
}
});
return false;
});
});
总结
太庞大复杂,不敢用在现有的项目当中,怕又出现新的坑,打算以后新的项目中尝试Spring Security框架。
常见问题
Q MyAuthenticationFilter不会被调用的问题
A
如果你使用了类似下面的语句
排除哪些url pattern spring security不检查权限,
则指定login路径的时候不能在public路径下,如下,下面用main代替了public:
问题解决。 SpringSecurity技术学习,更多知识请访问https://www.itkc8.com
参考资料
[1]《Spring Security and JSON Authentication》
继承UsernamePasswordAuthenticationFilter实现json形式的登录
http://stackoverflow.com/questions/19500332/spring-security-and-json-authentication
[2]失败返回json字符串
http://blog.csdn.net/jmppok/article/details/44832641