基于Spring

网友投稿 758 2022-11-13

基于Spring

目录一. 自定义实现二. 实现自定义登陆页面Spring-Security登陆表单提交过程那么异常一下是如何传递给前端的呢获取方式

实现效果如图所示:

首先公布实现代码

一. 自定义实现

import.org.springframework.security.core.userdetails.UserDetailsService类

并且抛出BadCredentialsException异常,否则页面无法获取到错误信息。

@Slf4j

@Service

public class MyUserDetailsServiceImpl implements UserDetailsService {

@Autowired

private PasswordEncoder passwordEncoder;

@Autowired

private UserService userService;

@Autowired

private PermissionService permissionService;

private String passwordParameter = "password";

@Override

public UserDetails loadUserByUsername(String username) throws AuthenticationException {

HttpServletRequest request = ContextHolderUtils.getRequest();

String password = request.getParameter(passwordParameter);

log.error("password = {}", password);

SysUser sysUser = userService.getByUsername(username);

if (null == sysUser) {

log.error("用户{}不存在", username);

throw new BadCredentialsException("帐号不存在,请重新输入");

}

// 自定义业务逻辑校验

if ("userli".equals(sysUser.getUsername())) {

throw new BadCredentialsException("您的帐号有违规记录,无法登录!");

}

// 自定义密码验证

if (!password.equals(sysUser.getPassword())){

throw new BadCredentialsException("密码错误,请重新输入");

}

List permissionList = permissionService.findByUserId(sysUser.getId());

List authorityList = new ArrayList<>();

if (!CollectionUtils.isEmpty(permissionList)) {

for (SysPermission sysPermission : permissionList) {

authorityList.add(new SimpleGrantedAuthority(sysPermission.getCode()));

}

}

User myUser = new User(sysUser.getUsername(), passwordEncoder.encode(sysUser.getPassword()), authorityList);

log.info("登录成功!用户: {}", myUser);

return myUser;

}

}

二. 实现自定义登陆页面

前提是,你们已经解决了自定义登陆页面配置的问题,这里不做讨论。

通过 thymeleaf 表达式获取错误信息(我们选择thymeleaf模板引擎)

登录

th:text="${session.SPRING_SECURITY_LAST_EXCEPTION.message}">

Spring-Security登陆表单提交过程

当用户从登录页提交账号密码的时候,首先由

org.springframework.security.web.authentication包下的UsernamePasswordAuthenticationFilter类attemptAuthentication()

方法来处理登陆逻辑。

public Authentication attemptAuthentication(HttpServletRequest request,

HttpServletResponse response) throws AuthenticationException {

if (postOnly && !request.getMethod().equals("POST")) {

throw new AuthenticationServiceException(

"Authentication method not supported: " + request.getMethod());

}

String username = obtainUsername(request);

String password = obtainPassword(request);

if (username == null) {

username = "";

}

if (password == null) {

password = "";

}

username = username.trim();

UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(

username, password);

// Allow subclasses to set the "details" property

setDetails(request, authRequest);

return this.getAuthenticationManager().authenticate(authRequest);

}

1. 该类内部默认的登录请求url是"/login",并且只允许POST方式的请求。

2. obtainUsername()方法参数名为"username"和"password"从HttpServletRequest中获取用户名和密码(由此可以找到突破口,我们可以在自定义实现的loadUserByUsername方法中获取到提交的账号和密码,进而检查正则性)。

3. 通过构造方法UsernamePasswordAuthenticationToken,将用户名和密码分别赋值给principal和credentials。

public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {

super((Collection)null);

this.principal = principal;

this.credentials = credentials;

this.setAuthenticated(false);

}

super(null)调用的是父类的构造方法,传入的是权限集合,因为目前还没有认证通过,所以不知道有什么权限信息,这里设置为null,然后将用户名和密码分别赋值给principal和credentials,同样因为此时还未进行身份认证,所以setAuthenticated(false)。

到此为止,用户提交的表单信息已加载完成,继续往下则是校验表单提交的账号和密码是否正确。

那么异常一下是如何传递给前端的呢

前面提到用户登录验证的过滤器是UsernamePasswordAuthenticationFilter,它继承自AbstractAuthenticationProcessingFilter。

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)

throws IOException, ServletException {

HttpServletRequest request = (HttpServletRequest) req;

HttpServletResponse response = (HttpServletResponse) res;

if (!requiresAuthentication(request, response)) {

chain.doFilter(request, response);

return;

}

if (logger.isDebugEnabled()) {

logger.debug("Request is to process authentication");

}

AuthenticatiqkDxodQSUEon authResult;

try {

authResult = attemptAuthentication(request, response);

if (authResult == null) {

// return immediately as subclass has indicated that it hasn't completed

// authentication

return;

}

sessionStrategy.onAuthentication(authResult, request, response);

}

catch (InternalAuthenticationServiceException failed) {

logger.error(

"An internal error occurred while trying to authenticate the user.",

failed);

unsuccessfulAuthentication(request, response, failed);

return;

}

catch (AuthenticationException failed) {

// Authentication failed

unsuccessfulAuthentication(request, response, failed);

return;

}

// Authentication success

if (continueChainBeforeSuccessfulAuthentication) {

chain.doFilter(request, response);

}

successfulAuthentication(request, response, chain, authResult);

}

从代码片段中看到Spring将异常捕获后交给了unsuccessfulAuthentication这个方法来处理。

unsuccessfulAuthentication又交给了failureHandler(AuthenticationFailureHandler)来处理,然后追踪failureHandler

protected void unsuccessfulAuthentication(HttpServletRequest request,

HttpServletResponse response, AuthenticationException failed)

throws IOException, ServletException {

SecurityContextHolder.clearContext();

if (logger.isDebugEnabled()) {

logger.debug("Authentication request failed: " + failed.toStringhttp://(), failed);

logger.debug("Updated SecurityContextHolder to contain null Authentication");

logger.debug("Delegating to authentication failure handler " + failureHandler);

}

rememberMeServices.loginFail(request, response);

failureHandler.onAuthenticationFailure(request, response, failed);

}

Ctrl + 左键 追踪failureHandler引用的类是,SimpleUrlAuthenticationFailureHandler。

private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();

private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();

找到SimpleUrlAuthenticationFailureHandler类中的,onAuthenticationFailure()方法。

public void onAuthenticationFailure(HttpServletRequest request,

HttpServletResponse response, AuthenticationException exception)

throws IOException, ServletException {

if (defaultFailureUrl == null) {

logger.debug("No failure URL set, sending 401 Unauthorized error");

response.sendError(HttpServletResponse.SC_UNAUTHORIZED,

"Authentication Failed: " + exception.getMessage());

}

else {

saveException(request, exception);

if (forwardToDestination) {

logger.debug("Forwarding to " + defaultFailureUrl);

request.getRequestDispatcher(defaultFailureUrl)

.forward(request, response);

}

else {

logger.debug("Redirecting to " + defaultFailureUrl);

redirectSthttp://rategy.sendRedirect(request, response, defaultFailureUrl);

}

}

}

追踪到saveException(request, exception)的内部实现。

protected final void saveException(HttpServletRequest request,

AuthenticationException exception) {

if (forwardToDestination) {

request.setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);

}

else {

HttpSession session = request.getSession(false);

if (session != null || allowSessionCreation) {

request.getSession().setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION,

exception);

}

}

}

此处的

request.getSession().setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);

就是存储到session中的错误信息,key就是

public static final String AUTHENTICATION_EXCEPTION =

"SPRING_SECURITY_LAST_EXCEPTION";

因此我们通过thymeleaf模板引擎的表达式可获得session的信息。

获取方式

th:text="${session.SPRING_SECURITY_LAST_EXCEPTION.message}">

需要注意:saveException保存的是Session对象所以需要使用${SPRING_SECURITY_LAST_EXCEPTION.message}获取。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:前端基础(4):html语法(3): 标签
下一篇:再出发,我的梦
相关文章

 发表评论

暂时没有评论,来抢沙发吧~