海宁网站建设公司推荐,游戏制作软件免费版,wordpress更换后台logo,搭建个人网站的步骤前后端分离模式是指由前端控制页面路由#xff0c;后端接口也不再返回html数据#xff0c;而是直接返回业务数据#xff0c;数据一般是JSON格式。Spring Security默认的表单登录方式#xff0c;在未登录或登录成功时会发起页面重定向#xff0c;在提交登录数据时#xff…前后端分离模式是指由前端控制页面路由后端接口也不再返回html数据而是直接返回业务数据数据一般是JSON格式。Spring Security默认的表单登录方式在未登录或登录成功时会发起页面重定向在提交登录数据时也不是JSON格式。要支持前后端分离模式要对这些问题进行改造。1. 认证信息改成JSON格式Spring Security默认提供账号密码认证方式具体实现是在UsernamePasswordAuthenticationFilter 中。因为是表单提交所以Filter中用request.getParameter(this.usernameParameter) 来获取用户信息。当我们将数据改成JSON并放入HTTP Body后getParameter 就没法获取到信息。要解决这个问题就要新建Filter来替换UsernamePasswordAuthenticationFilter 然后覆盖掉获取用户的方法。1.1 新建JsonUsernamePasswordAuthenticationFilterimport com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.SneakyThrows;
import org.springframework.data.util.Pair;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;public class JsonUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)throws AuthenticationException {if (!request.getMethod().equals(POST)) {throw new AuthenticationServiceException(Authentication method not supported: request.getMethod());}PairString, String usernameAndPassword obtainUsernameAndPassword(request);String username usernameAndPassword.getFirst();username (username ! null) ? username.trim() : ;String password usernameAndPassword.getSecond();password (password ! null) ? password : ;UsernamePasswordAuthenticationToken authRequest UsernamePasswordAuthenticationToken.unauthenticated(username,password);// Allow subclasses to set the details propertysetDetails(request, authRequest);return this.getAuthenticationManager().authenticate(authRequest);}SneakyThrowsprivate PairString, String obtainUsernameAndPassword(HttpServletRequest request) {JSONObject map JSON.parseObject(request.getInputStream(), JSONObject.class);return Pair.of(map.getString(getUsernameParameter()), map.getString(getPasswordParameter()));}
}1.2 新建JsonUsernamePasswordLoginConfigurer注册Filter有两种方式一给是直接调用httpSecurity的addFilterAt(Filter filter, Class? extends Filter atFilter) 另一个是注册通过AbstractHttpConfigurer 来注册。我们选择第二种方式来注册Filter因为AbstractHttpConfigurer 在初始化 UsernamePasswordAuthenticationFilter 的时候会额外设置一些信息。新建一个自己的AbstractHttpConfigurerimport org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;public final class JsonUsernamePasswordLoginConfigurerH extends HttpSecurityBuilderH extendsAbstractAuthenticationFilterConfigurerH, JsonUsernamePasswordLoginConfigurerH, JsonUsernamePasswordAuthenticationFilter {public JsonUsernamePasswordLoginConfigurer() {super(new JsonUsernamePasswordAuthenticationFilter(), null);}Overrideprotected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) {return new AntPathRequestMatcher(loginProcessingUrl, POST);}
}1.3 注册JJsonUsernamePasswordLoginConfigurer到HttpSecurity这一步比较简单直接关闭表单登录然后注册我们自己的Filter。http.formLogin().disable().apply(new JsonUsernamePasswordLoginConfigurer())经过这三步Spring Security就能识别JSON格式的用户信息。2. 去掉重定向有几个场景会触发Spring Security的重定向未登录重定向到登录页面登录验证成功重定向到默认页面退出登录重定向到默认页面我们要对这几个场景分别处理给前端返回错误信息而不是重定向。2.1 未登录请求未登录的请求会被AuthorizationFilter拦截并抛出异常。异常被AuthenticationEntryPoint处理默认会触发重定向到登录页。我们通过自定义AuthenticationEntryPoint来取消重定向行为改为返回JSON信息。http
// 1. 未登录的请求会被AuthorizationFilter拦截并抛出异常。
.exceptionHandling(it - it.authenticationEntryPoint((request, response, authException) - {log.info(get exception {}, authException.getClass());String msg {\\msg\\: \\用户未登录\\};response.setStatus(HttpStatus.FORBIDDEN.value());response.setContentType(MediaType.APPLICATION_JSON_VALUE);PrintWriter writer response.getWriter();writer.write(msg);writer.flush();writer.close();
}))2.2 登录成功/失败登录成功或失败后的行为由AuthenticationSuccessHandler 和AuthenticationFailureHandler 来控制。由于上面我们自定义了JsonUsernamePasswordLoginConfigurer 所以要配置自定义Configurer 上的AuthenticationSuccessHandler 和AuthenticationFailureHandler 。http.formLogin().disable().apply((SecurityConfigurerAdapter) new JsonUsernamePasswordLoginConfigurer().successHandler((request, response, authentication) - {String msg {\\msg\\: \\登录成功\\};response.setStatus(HttpStatus.OK.value());response.setContentType(MediaType.APPLICATION_JSON_VALUE);PrintWriter writer response.getWriter();writer.write(msg);writer.flush();writer.close();}).failureHandler((request, response, exception) - {String msg {\\msg\\: \\用户名密码错误\\};response.setStatus(HttpStatus.UNAUTHORIZED.value());response.setContentType(MediaType.APPLICATION_JSON_VALUE);PrintWriter writer response.getWriter();writer.write(msg);writer.flush();writer.close();}));2.3 退出登录// 退出登录
.logout(it - it.logoutSuccessHandler((request, response, authentication) - {String msg {\\msg\\: \\退出成功\\};response.setStatus(HttpStatus.OK.value());response.setContentType(MediaType.APPLICATION_JSON_VALUE);PrintWriter writer response.getWriter();writer.write(msg);writer.flush();writer.close();}))3. 最后处理CSRF校验由于前端直接调用登录接口跳过了获取登录页面的步骤所以服务端没有机会将CSRF Token传给前段所以要把POST /login接口的CSRF校验剔除掉。http.csrf(it - it.ignoringRequestMatchers(/login, POST))