SpringSecurity学习四-自定义Login请求和返回的数据格式

本文介绍了如何在SpringSecurity中自定义登录请求格式,如JSON,并处理登录过程中的各种情况,包括验证验证码、权限错误、非法数据的JSON响应,以及实现单账号登录等。通过修改特定的过滤器和处理器,实现了详细的登录流程控制,并提供了配置文件和关键代码示例。

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{
	//当访问的资源没有权限,会调用这里
	@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<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不会被调用的问题

如果你使用了类似下面的语句

<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

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kagula086

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值