因为我们调用方法http.formLogin()开启了用户未登录状态就请求资源将被重定向到登陆页面的功能,但是跳转的登陆页面是spring security自带的一个登陆页面,这样显然不是我们想要的,我们想要的必然是身份验证要走我们自己写的更加好看的登陆页
怎么定制自己的登录页呢?查看http.formLogin()的源码
通过源码我们可以发现,这个方法在父类中的实现果然还是没有参考价值,有参考价值的还是方法上的注释
* Specifies to support form based authentication. If * {@link FormLoginConfigurer#loginPage(String)} is not specified a default login page * will be generated. * 支持用户指定基于表单的身份验证,就是支持我们自己指定一个表单页面来进行身份认证 * 如果用户没有指定,将使用spring security自带的身份认证表单页面 * The configuration below demonstrates customizing the defaults. * 下面的配置演示如何自定义默认值 * * * @Configuration * @EnableWebSecurity * public class FormLoginSecurityConfig extends WebSecurityConfigurerAdapter { * * @Override * protected void configure(HttpSecurity http) throws Exception { * http.authorizeRequests().antMatchers("/**").hasRole("USER").and().formLogin() * .usernameParameter("username") // default is username * .passwordParameter("password") // default is password * //get方式请求的默认login页面,我们就可以通过这个方法指定我们自己的登录页为身份认证页面 * //注意:loginPage()中指定的是这个页面的请求url,不是资源真实地址 * .loginPage("/authentication/login") // default is /login with an HTTP get * .failureUrl("/authentication/login?failed") // default is /login?error * .loginProcessingUrl("/authentication/login/process"); // default is /login * // with an HTTP * // post * }指定我们自己的登录页为spring security的身份认证页面
http.formLogin().loginPage("/toLogin");现在我们指定了spring security的身份认证页面了,那么页面应该把数据提交到哪里才能实现身份认证呢?
【解决】看http.formLogin()的源码,看看原原来默认的身份认证页面的action属性是什么,直接拷贝过来使用
public FormLoginConfigurer<HttpSecurity> formLogin() throws Exception { return getOrApply(new FormLoginConfigurer<>()); }可见这个方法只是new了一个对象FormLoginConfigurer,我们去查看这个类的源码;注意:下面的源码只是节选了我们自己需要的部分
public final class FormLoginConfigurer<H extends HttpSecurityBuilder<H>>...{ //构造 public FormLoginConfigurer() { super(new UsernamePasswordAuthenticationFilter(), null); usernameParameter("username"); passwordParameter("password"); } /** * <p> * Specifies the URL to send users to if login is required. If used with * {@link WebSecurityConfigurerAdapter} a default login page will be generated when * this attribute is not specified. * </p> * * <p> * If a URL is specified or this is not being used in conjuction with * {@link WebSecurityConfigurerAdapter}, users are required to process the specified * URL to generate a login page. In general, the login page should create a form that * submits a request with the following requirements to work with * {@link UsernamePasswordAuthenticationFilter}: * </p> * * <ul> * <li>It must be an HTTP POST</li> * <li>It must be submitted to {@link #loginProcessingUrl(String)}</li> * <li>It should include the username as an HTTP parameter by the name of * {@link #usernameParameter(String)}</li> * <li>It should include the password as an HTTP parameter by the name of * {@link #passwordParameter(String)}</li> * </ul> * * <h2>Example login.jsp</h2> * * Login pages can be rendered with any technology you choose so long as the rules * above are followed. Below is an example login.jsp that can be used as a quick start * when using JSP's or as a baseline to translate into another view technology. * * <pre> * <!-- loginProcessingUrl should correspond to FormLoginConfigurer#loginProcessingUrl. Don't forget to perform a POST --> * <c:url value="/login" var="loginProcessingUrl"/> * <form action="${loginProcessingUrl}" method="post"> * <fieldset> * <legend>Please Login</legend> * <!-- use param.error assuming FormLoginConfigurer#failureUrl contains the query parameter error --> * <c:if test="${param.error != null}"> * <div> * Failed to login. * <c:if test="${SPRING_SECURITY_LAST_EXCEPTION != null}"> * Reason: <c:out value="${SPRING_SECURITY_LAST_EXCEPTION.message}" /> * </c:if> * </div> * </c:if> * <!-- the configured LogoutConfigurer#logoutSuccessUrl is /login?logout and contains the query param logout --> * <c:if test="${param.logout != null}"> * <div> * You have been logged out. * </div> * </c:if> * <p> * <label for="username">Username</label> * <input type="text" id="username" name="username"/> * </p> * <p> * <label for="password">Password</label> * <input type="password" id="password" name="password"/> * </p> * <!-- if using RememberMeConfigurer make sure remember-me matches RememberMeConfigurer#rememberMeParameter --> * <p> * <label for="remember-me">Remember Me?</label> * <input type="checkbox" id="remember-me" name="remember-me"/> * </p> * <div> * <button type="submit" class="btn">Log in</button> * </div> * </fieldset> * </form> * </pre> * * <h2>Impact on other defaults</h2> * * Updating this value, also impacts a number of other default values. For example, * the following are the default values when only formLogin() was specified. * * <ul> * <li>/login GET - the login form</li> * <li>/login POST - process the credentials and if valid authenticate the user</li> * <li>/login?error GET - redirect here for failed authentication attempts</li> * <li>/login?logout GET - redirect here after successfully logging out</li> * </ul> * * If "/authenticate" was passed to this method it update the defaults as shown below: * * <ul> * <li>/authenticate GET - the login form</li> * <li>/authenticate POST - process the credentials and if valid authenticate the user * </li> * <li>/authenticate?error GET - redirect here for failed authentication attempts</li> * <li>/authenticate?logout GET - redirect here after successfully logging out</li> * </ul> * * * @param loginPage the login page to redirect to if authentication is required (i.e. * "/login") * @return the {@link FormLoginConfigurer} for additional customization */ @Override public FormLoginConfigurer<H> loginPage(String loginPage) { return super.loginPage(loginPage); } }上面的源码中我只选出了FormLoginConfigurer的构造和一个成员方法loginPage(),loginPage()显然就是配置spring security的login页面的,但是这个方法本身实现没有什么好注意的,值得注意的是它上面的注释,它上面的注释大概的意思就是说如果我们在设置spring security的登录页的时候没有显式的配置一个我们自己的login page,那么spring security将会自动为我们生成一个登陆表单页面,并且它把这个表单的代码也粘贴在了注释里,但是由于编码问题,html页面的表单的< >两个尖括号变成了& lt ;,这就导致我们阅读起来比较有障碍,但是我们还是可以从注释中找到表单的定义,或者我们可以使用markdowm编辑器查看,这样就会还原表单的定义
注释中说明的自动生成的表单视图中表单的定义为:<form action="${loginProcessingUrl}" method="post">
从表单定义中我们可以发现,表单提交方式为POST,提交的地址为${loginProcessingUrl},所以我们可以照猫画虎,将我们自定义的登录页的表单提交地址改成这个地址,这样加上我们在config中自定义了spring security的登录页,我们就可以从自定义的登录页提交用户名+密码给spring security完成我们的身份"认证"
<form th:action="${loginProcessingUrl}" method="post"> 测试 从上面的结果来看,我们设置的表单提交地址完全正确搞完表单地址,我们来说说传递的用户名和密码后端怎么接收的,这里就需要看看FormLoginConfigurer类的构造了,在上面粘贴源码的时候我特意把它也粘贴了出来public FormLoginConfigurer() { super(new UsernamePasswordAuthenticationFilter(), null); usernameParameter("username"); passwordParameter("password"); } 注意:从上面的代码来看,应该就是后端接收前端传递的用户名+密码的时候是按照username+password两个名称来进行接收的,我们可以去自定义的表单页看看我们的form表单中的两个input标签的name属性 可见,账号+密码命名与后端接收参数的名称一致,所以spring security才能接收到我们在表单中填写的账号+密码;但是光是嘴上说肯定不行,我们需要来检测我们的结论是否正确检测办法:把前端input标签的name属性改了,看看我们提交的用户数据能不能通过spring security的"认证" 再次登陆测试 那么我们前端的input的name属性就只能是username+password?当然不是,spring security将变量的命名权力都交给了我们,我们只需要调用对应的方法即可设置变量名方法是什么?看formLogin()的源码 * @Override * protected void configure(HttpSecurity http) throws Exception { * http.authorizeRequests().antMatchers("/**").hasRole("USER").and().formLogin() * .usernameParameter("username") // default is username * .passwordParameter("password") // default is password * .loginPage("/authentication/login") // default is /login with an HTTP get * .failureUrl("/authentication/login?failed") // default is /login?error * .loginProcessingUrl("/authentication/login/process"); // default is /login * // with an HTTP * // post * } 设置接收的变量名与前端一致http.formLogin().loginPage("/toLogin").usernameParameter("uname").passwordParameter("pwd"); 再次测试登陆 测试成功!测试法1:直接在config中加一条认证配置http.csrf().disable();
测试成功!
我们对源码中提到的3种方法都进行了测试,可以发现3种方法都可以解决spring security种自定义了登陆页面之后,注销功能不能使用的情况,官方从源码中对于3种方法的使用推荐为:使用POST方式提交 > 配置logoutRequestMatcher(new AntPathRequestMatcher("/logout",“GET”)) > 关闭CSRF攻击保护