前后端不分离情况
1.登录情况
配置登录成功的处理通过两个方法来配置的:
defaultSuccessUrl
successForwardUrl
这两个方法都是配置跳转地址的,适用于前后端不分开的开发,除了这两个方法还有一个必杀技“successHandler”,他基本上包括了defaultSuccessUrl和successForwardUrl两个方法
2.登录成功情况
.successHandler((req, resp, authentication) -> {
Object principal = authentication.getPrincipal();
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
out.write(new ObjectMapper().writeValueAsString(principal));
out.flush();
out.close();
})
successHandler方法参数是一个AuthenticationSuccessHandler对象,这个对象中我们要实现的方法是onAuthenticationSuccess.
onAuthenticationSuccess方法有三个参数,分别是:
HttpServletRequest
HttpServletResponse
Authentication
有了前两个参数,我们可以随心所欲返回数据了,利用HttpServletRequest可以做服务端跳转,利用HttpServletResponse可以做客户端跳转,当然,也可以返回JSON数据。
3.登录失败情况
.failureHandler((req, resp, e) -> {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
out.write(e.getMessage());
out.flush();
out.close();
})
第三个参数是exception,exception包岑登录失败原因,可以通过JSON返回前端
在一般的项目中,会写出异常的类型,根据不同的类型,可以给客户明确的提示
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
RespBean respBean = RespBean.error(e.getMessage());
if (e instanceof LockedException) {
respBean.setMsg("账户被锁定,请联系管理员!");
} elseif (e instanceof CredentialsExpiredException) {
respBean.setMsg("密码过期,请联系管理员!");
} elseif (e instanceof AccountExpiredException) {
respBean.setMsg("账户过期,请联系管理员!");
} elseif (e instanceof DisabledException) {
respBean.setMsg("账户被禁用,请联系管理员!");
} elseif (e instanceof BadCredentialsException) {
respBean.setMsg("用户名或者密码输入错误,请重新输入!");
}
out.write(new ObjectMapper().writeValueAsString(respBean));
out.flush();
out.close();
3.登录失败情况
当用户输入用户名密码不正确时,我们会给一个模糊的提示(用户名或密码错误)
在SpringSecurity中用户名查找失败的异常是
UsernameNotFoundException
密码匹配失败的异常是
BadCredentialsException
但是在登录失败后,看不到UsernameNotFoundException异常,无论用户名还是密码错误,跑出的异常都是 BadCredentialsException ,这是为什么呢?让我们一探究竟。
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
try {
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
}
catch (UsernameNotFoundException notFound) {
logger.debug("User '" + username + "' not found");
if (hideUserNotFoundExceptions) {
thrownew BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
else {
throw notFound;
}
}
}
4.未登录访问数据情况
在前后端未分离情况下,如果用户未登录就访问数据,我们直接跳到登录页面就完事,但是在前后端分离中咱们不能这么干,前后端分离我们不能让用户重定向到登录界面,而是给用户一个未登录提示,前端收到提示,再自己决定页面跳转。
想解决这个问题,就涉及到Spring Security中的一个接口AuthenticationEntrypoint,该接口有一个实现类LoginUrlAuthenticationEntryPoint,该类中有一个commence方法:
/**
* Performs the redirect (or forward) to the login form URL.
*/
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) {
String redirectUrl = null;
if (useForward) {
if (forceHttps && "http".equals(request.getScheme())) {
redirectUrl = buildHttpsRedirectUrlForRequest(request);
}
if (redirectUrl == null) {
String loginForm = determineUrlToUseForThisRequest(request, response,
authException);
if (logger.isDebugEnabled()) {
logger.debug("Server side forward to: " + loginForm);
}
RequestDispatcher dispatcher = request.getRequestDispatcher(loginForm);
dispatcher.forward(request, response);
return;
}
}
else {
redirectUrl = buildRedirectUrlToLoginPage(request, response, authException);
}
redirectStrategy.sendRedirect(request, response, redirectUrl);
}
5.注销登录
注销登录也和访问数据时未登录一样,注销登录成功后返回JSON ,后端只需要给提示
代码如下:
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessHandler((req, resp, authentication) -> {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
out.write("注销成功");
out.flush();
out.close();
})
.permitAll()
.and()
6.整体代码
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("zhuzm")
.password("123").roles("admin");
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/js/**", "/css/**", "/images/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/doLogin")
.usernameParameter("name")
.passwordParameter("passwd")
.successHandler((req, resp, authentication) -> {
Object principal = authentication.getPrincipal();
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
out.write(new ObjectMapper().writeValueAsString(principal));
out.flush();
out.close();
})
.failureHandler((req, resp, e) -> {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
out.write(e.getMessage());
out.flush();
out.close();
})
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessHandler((req, resp, authentication) -> {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
out.write("注销成功");
out.flush();
out.close();
})
.permitAll()
.and()
.csrf().disable().exceptionHandling()
.authenticationEntryPoint((req, resp, authException) -> {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
out.write("尚未登录,请先登录");
out.flush();
out.close();
}
);
}
}