SpringSecurity学习四-自定义Login请求和返回的数据格式
来源:互联网 发布:网络ip在哪里设置 编辑:程序博客网 时间:2024/03/29 17:34
Author: Kagula
Date: 2016-10-09
环境
[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
<?xml version="1.0" encoding="UTF-8"?><web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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"> <display-name>Archetype Created Web Application</display-name> <!-- 配置字符集过滤器 --> <!-- 必须配置在所有过滤器的前面 --> <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> <!-- 配置项目的编码mapping --> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 如果同一个bean被定义两次,后面一个优先 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring-servlet.xml,/WEB-INF/spring-security.xml</param-value> </context-param> <!-- 启动spring容器用,容器用于管理Bean --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- Spring Security会话控制 --> <listener> <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class> </listener> <!-- Spring security 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> <!-- DispatcherServlet 针对MVC上下文加载,即拦截请求,分发请求给Controller --> <!-- 《ContextLoaderListener初始化的前后文和DispatcherServlet初始化的上下文关系》 http://www.educity.cn/wenda/356953.html --> <servlet> <servlet-name>spring</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <!-- url-pattern配置为/,不带文件后缀,会造成其它静态文件(js,css等)不能访问。如配为*.do,则不影响静态文件的访问 --> <servlet-mapping> <servlet-name>spring</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> <error-page> <error-code>404</error-code> <location>/My404.jsp</location> </error-page> <error-page> <exception-type>java.lang.Exception</exception-type> <location>/MyEception.jsp</location> </error-page> </web-app>
spring-security.xml
<?xml version="1.0" encoding="UTF-8"?> <b:beans xmlns="http://www.springframework.org/schema/security" 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"> <http pattern="/main/customLogin.do" security="none" /> <http access-denied-page="/accessDenied.jsp" entry-point-ref="authenticationEntryPoint"> <logout logout-url="/j_spring_security_logout" logout-success-url="/main/customLogin.do" invalidate-session="true" /> <intercept-url pattern="/main/welcome.do" access="ROLE_ADMIN" /> <custom-filter ref="myFilter" before="FILTER_SECURITY_INTERCEPTOR"/> <!-- 这里添加自己定义的AuthenticationFilter到FilterChain的FORM_LOGIN_FILTER位置 --> <!-- 所以上面不需要定义form-login属性了 --> <custom-filter ref="myAuthenticationFilter" position="FORM_LOGIN_FILTER"/> <custom-filter ref="concurrencyFilter" position="CONCURRENT_SESSION_FILTER" /> <session-management session-authentication-strategy-ref="sessionAuthenticationStrategy"> </session-management> </http> <b:bean id="concurrencyFilter" class="org.springframework.security.web.session.ConcurrentSessionFilter"> <b:property name="sessionRegistry" ref="sessionRegistry" /> <b:property name="expiredUrl" value="/sessionExpired.jsp" /> </b:bean> <!-- 在MyAuthenticationFilter中可以自定义数据的请求格式 --> <b:bean id="myAuthenticationFilter" class="com.nuoke.MyAuthenticationFilter"> <b:property name="authenticationManager" ref="authenticationManager" /> <b:property name="sessionAuthenticationStrategy" ref="sessionAuthenticationStrategy" /> <b:property name="usernameParameter" value="username"/> <b:property name="passwordParameter" value="password"/> <b:property name="filterProcessesUrl" value="/main/customLogin2.do" /> <b:property name="authenticationSuccessHandler"> <b:bean class="com.nuoke.MyAuthenticationSuccessHandler"> <!-- 不能设置/WEB-INF下的jsp,会访问不到,虽然服务端Console不会打印错误信息 --> <!-- 但是客户端也不会收到你指定的jsp信息 --> <b:property name="defaultTargetUrl" value="/customLoginResponse.jsp"></b:property> </b:bean> </b:property> <b:property name="authenticationFailureHandler"> <b:bean class="com.nuoke.MyAuthenticationFailureHandler"> <!-- 不能设置/WEB-INF下的jsp,会访问不到,虽然服务端Console不会打印错误信息 --> <!-- 但是客户端也不会收到你指定的jsp信息 --> <b:property name="defaultFailureUrl" value="/customLoginResponse.jsp"></b:property> </b:bean> </b:property> </b:bean> <!-- 若访问没有权限,自动跳到下面指定的页面 --> <b:bean id="authenticationEntryPoint" class="com.nuoke.MyAuthenticationEntryPoint"> <b:property name="loginFormUrl" value="/main/customLogin.do" /> </b:bean> <!--一个自定义的filter,必须包含 authenticationManager,accessDecisionManager,securityMetadataSource三个属性, 我们的所有控制将在这三个类中实现,解释详见具体配置 --> <b:bean id="myFilter" class="com.nuoke.MyFilterSecurityInterceptor"> <b:property name="authenticationManager" ref="authenticationManager" /> <b:property name="accessDecisionManager" ref="myAccessDecisionManagerBean" /> <b:property name="securityMetadataSource" ref="securityMetadataSource" /> </b:bean> <!--验证配置,认证管理器,实现用户认证的入口,主要实现UserDetailsService接口即可 --> <authentication-manager alias="authenticationManager"> <authentication-provider user-service-ref="myUserDetailService"> <!--如果用户的密码采用加密的话 --> <password-encoder hash="md5"/> </authentication-provider> </authentication-manager> <!--在这个类中,你就可以从数据库中读入用户的密码,角色信息,是否锁定,账号是否过期等 --> <b:bean id="myUserDetailService" class="com.nuoke.MyUserDetailService" /> <!--访问决策器,决定某个用户具有的角色,是否有足够的权限去访问某个资源 --> <b:bean id="myAccessDecisionManagerBean" class="com.nuoke.MyAccessDecisionManager"> </b:bean> <b:bean id="sessionAuthenticationStrategy" class="org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy"> <b:constructor-arg name="sessionRegistry" ref="sessionRegistry" /> <b:property name="maximumSessions" value="1" /> <b:property name="exceptionIfMaximumExceeded" value="false" /> </b:bean> <b:bean id="sessionRegistry" class="org.springframework.security.core.session.SessionRegistryImpl" /> <!--资源数据定义,将所有的资源和权限对应关系建立起来,即定义某一资源可以被哪些角色访问 --> <b:bean id="securityMetadataSource" class="com.nuoke.MyInvocationSecurityMetadataSource" /> </b:beans>
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{//当访问的资源没有权限,会调用这里@Overridepublic 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<Object> principals = sessionRegistry.getAllPrincipals();List<String> usersNamesList = new ArrayList<String>();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"%><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>自定义登录控制</title><link href="../common/bootstrap/css/bootstrap.min.css" rel="stylesheet"><link href="../common/bootstrap/css/bootstrap-theme.min.css" rel="stylesheet"><script type="text/javascript"src="../common/bootstrap/js/bootstrap.min.js"></script><script type="text/javascript" src="../common/jquery/jquery-2.1.1.min.js"></script><body><div class="container"><div id="container_demo"><div id="wrapper"><div id="login" class="animate form"><h1>示例二 自定义login方法</h1><form id='loginForm' method="POST"><p><label for="" class="uname" data-icon="u"> 用户名 </label> <input id="username" name="username" required="required" type="text" placeholder="myusername or mymail@mail.com"></p><p><label for="" class="youpasswd" data-icon="p"> 密码 </label><input id="password" name="password" required="required" type="password" placeholder="eg. X8df!90EO"></p><p class="login button"><input type="submit" id="submitId" value="登录"></p></form></div></div></div></div></body><script type="text/javascript"> $(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("<div class='error'><font color='red'>"+result.msg+"</font></div>"); } }, error:function(XMLHttpRequest, textStatus, errorThrown){ alert('读取超时,请检查网络连接...'); } }); return false; }); }); </script></html>
总结
太庞大复杂,不敢用在现有的项目当中,怕又出现新的坑,打算以后新的项目中尝试Spring Security框架。
常见问题
Q MyAuthenticationFilter不会被调用的问题
A
如果你使用了类似下面的语句
<http pattern="/public/**" security="none"/>
排除哪些url pattern spring security不检查权限,
则指定login路径的时候不能在public路径下,如下,下面用main代替了public:
<beans:bean id="myAuthenticationFilter" ... ...
<beans:property name="filterProcessesUrl" value="/main/login.do" />
问题解决。
参考资料
[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
- SpringSecurity学习四-自定义Login请求和返回的数据格式
- SpringSecurity学习笔记之四:拦截请求
- 网络请求返回数据格式
- SpringSecurity学习笔记(四)——更改SpringSecurity加密方式
- jquery发送ajax请求返回数据格式
- ajax请求数据,返回json数据格式
- SpringSecurity 学习记录(一)- SpringSecurity 主要的实现类
- php学习笔记(三十)ajax请求和接收参数的实现方式(包括json数据格式的简单处理)
- Http的请求和返回
- SpringSecurity中自定义过滤器的种类
- SpringSecurity(四):自定义登陆认证实现手机号登陆
- RxJava Retrofit2 网络请求,返回数据格式统一的数据报文处理方法
- RxJava Retrofit2 网络请求,返回数据格式统一的数据报文处理方法
- ajax发送请求后台处理数据格式然后返回前台的方法(ObjectMapper)
- JavaScript学习笔记(四) 自定义构造函数和构造函数返回值
- thinkphp返回Json的数据格式
- renderJson()返回的数据格式
- iOS音频系列(四)--音频的文件格式和数据格式
- Xcode8 的模拟器怎么没有了
- MAVEN导入依赖的Jar包时Index downloads are disabled, search results may be incomplete问题的解决
- OSI七层模型图解
- 微信开通检测工具如何检测效果最好
- UNP——Chapter 4:基本TCP套接字编程
- SpringSecurity学习四-自定义Login请求和返回的数据格式
- 程序包 javax.servlet 不存在 解决办法
- 小白日记20:kali渗透测试之后渗透测试阶段(一)--上传工具
- Amr and Pins
- 硬件看门狗和软件看门狗
- iOS-iOS10新特性引发的崩溃
- Java String类一些操作的内存问题
- FFmpeg中的时间戳(DTS和PTS)
- Kafka使用方法