springboot+springSecurity+thymleaf 实现页面登录拦截案例

项目地址https://github.com/luozijing/springboot-/tree/dev

目录

项目地址https://github.com/luozijing/springboot-/tree/dev

登录页面

springSecurity执行原理

部分源码

springSecurity配置

验证成功处理器

用户账号权限验证类

thymleaf+bootstrap模板


登录页面

 

登录成功,角色为role1,可以设计不同的角色,能够显示不同的角色信息。

springSecurity执行原理

security流程主要由过滤器链组成,其中比较要的过滤器由AbstractAuthenticationProcessingFilter ,它的作用是帮助我们验证用户权限和密码的正确。

 

验证流程

https://blog.csdn.net/weixin_34161064/article/details/93164080

在这里插入图片描述

UsernamePasswordAuthenticationFilter是其的子类,用户验证流程如下所示。

在这里插入图片描述

 


进入 ProviderManager 类后会调用 authenticate(Authentication authentication) 方法,它通过 AuthenticationProvider 实现类获取用户的登录的方式,然后会有一个 while 迭代器模式的循环遍历,检查它是否支持这种登录方式,具体的登录方式有表单登录,qq登录,微信登录等。如果最终都不支持会抛出相应的异常信息,如果支持则会进入AuthenticationProvider 接口的抽象实现类 AbstractUserDetailsAuthenticationProvider 中。

进入 AbstractUserDetailsAuthenticationProvider 类后会调用 authenticate(Authentication authentication) 方法对用户的身份进行校验,首先是判断用户是否为空,这个 user 是 UserDetail 的对象,如果为空,表示还没有认证,就需要调用 retrieveUser 方法去获取用户的信息,这个方法是抽象类 AbstractUserDetailsAuthenticationProvider 的扩展类DaoAuthenticationProvider 的一个方法。

在该扩展类的 retrieveUser 方法中调用 UserDetailsService 这个接口的实现类的 loadUserByUsername 方法去获取用户信息,而这里我自己编写了实现类 UserDetailsServiceImpl 类,在这个实现类中,我们可以编写自己的逻辑,从数据库中获取用户密码等权限信息返回。

本地 UserDetailService 实现类 UserDetailsServiceImpl(我们经常需要用到的实现类)

此时 authorities 不再为空了。

在 UsernamePasswordAuthenticationToken 的父类中,它会检查用的权限,如果有一个为 null,表示权限没有相应的权限,抛出异常。

然后在 createSuccessAuthentication 方法返回后回到 ProvioderManager 的 authenticate 方法中返回 result,最后回到UsernamePasswordAuthenticationFilter 的刚开始进入的 attemptAuthentication 方法中返回。

attemptAuthentication() 方法中的返回,返回到哪?

再回到第一张图,UsernamePasswordAuthenticationFilter 父类 doFilter() 方法,返回值就是 authResult,如果过程中发现存在异常则执行 unsuccessfulAuthentication.onAuthenticationFailure() 方法,如果认证成功则执行 successfulAuthentication.onAuthenticationSuccess() 方法,再结合上边提到的自定义 成功/失败处理类。

 

 

public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean implements ApplicationEventPublisherAware, MessageSourceAware {
    protected ApplicationEventPublisher eventPublisher;
    protected AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
    private AuthenticationManager authenticationManager;
    protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
    private RememberMeServices rememberMeServices = new NullRememberMeServices();
    private RequestMatcher requiresAuthenticationRequestMatcher;
    private boolean continueChainBeforeSuccessfulAuthentication = false;
    private SessionAuthenticationStrategy sessionStrategy = new NullAuthenticatedSessionStrategy();
    private boolean allowSessionCreation = true;
    private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
    private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();

    protected AbstractAuthenticationProcessingFilter(String defaultFilterProcessesUrl) {
        this.setFilterProcessesUrl(defaultFilterProcessesUrl);
    }

    protected AbstractAuthenticationProcessingFilter(RequestMatcher requiresAuthenticationRequestMatcher) {
        Assert.notNull(requiresAuthenticationRequestMatcher, "requiresAuthenticationRequestMatcher cannot be null");
        this.requiresAuthenticationRequestMatcher = requiresAuthenticationRequestMatcher;
    }

    public void afterPropertiesSet() {
        Assert.notNull(this.authenticationManager, "authenticationManager must be specified");
    }

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse)res;
        if (!this.requiresAuthentication(request, response)) {
            chain.doFilter(request, response); //不需要用户验证的话执行下一个过滤链
        } else {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Request is to process authentication");
            }
            Authentication authResult;
            try {
                authResult = this.attemptAuthentication(request, response); //验证用户
                if (authResult == null) {
                    return;
                }

                this.sessionStrategy.onAuthentication(authResult, request, response);
            } catch (InternalAuthenticationServiceException var8) {
            
            	//如果抛出这两个异常,则表明登陆失败。进入这个方法 unsuccessfulAuthentication()
            
                this.logger.error("An internal error occurred while trying to authenticate the user.", var8);
                this.unsuccessfulAuthentication(request, response, var8);
                return;
            } catch (AuthenticationException var9) {
                this.unsuccessfulAuthentication(request, response, var9); //出现错误的话执行错误处理
                return;
            }

            if (this.continueChainBeforeSuccessfulAuthentication) {
                chain.doFilter(request, response);
            }
			//登陆成功之后,调用此方法 successfulAuthentication()
            this.successfulAuthentication(request, response, chain, authResult); 
        }
    }

}

认证之后的成功(失败)处理 来到最开始的AbstractAuthenticationProcessingFilter的doFilter中
如果期间没发成异常,则认证成功,调用successfulAuthentication()方法,进而调用SuccessHandler.onAuthenticationSuccess()方法。

具体源码可以流程可以参考这个

springSecurity配置

package com.students.springbootstu.security.config;

import com.students.springbootstu.configuration.SwaggerConfig;
import com.students.springbootstu.security.handler.MyAuthenctiationFailureHandler;
import com.students.springbootstu.security.handler.MyAuthenticationSuccessHandler;
import com.students.springbootstu.security.handler.MyLoginUrlAuthenticationEntryPoint;
import com.students.springbootstu.security.handler.MyLogoutSuccessHandler;
import com.students.springbootstu.security.service.MyUserDetailService;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * web相关安全配置
     */

    @Autowired
    //配置属性类
    private SecurityProperties securityProperties;

    @Autowired
    //用户账号权限验证类
    private MyUserDetailService myUserDetailService;

    @Autowired
    //注销成功处理器
    private MyLogoutSuccessHandler myLogoutSuccessHandler;

    @Autowired
    //成功验证处理器
    private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;

    @Value("${swagger.enable}")
    private boolean swaggerEnable;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        //loginPage("/testlogin") 相当于一个普通请求。这里则需要发送到登陆页面
        //loginProcessingUrl("/login") 登陆页面表单提交的action.必须为post请求
        //defaultSuccessUrl("/index") 登陆成功后,发送这个请求
       http
               .authorizeRequests()
               //设置匿名访问的url
               .antMatchers(securityProperties.getAnonymous()).permitAll()
               //所有请求都要验证
               .anyRequest().authenticated()
               .and()
               .formLogin()
               //自定义登录页
               .loginPage(securityProperties.getLoginPage())
               .successHandler(myAuthenticationSuccessHandler)
               //用户验证失败处理
               .failureHandler(new MyAuthenctiationFailureHandler(securityProperties.getLoginPage()))
               //自定义登录请求 登录页面表单提交的action.必须为post请求
               .loginProcessingUrl(securityProperties.getLoginProcessingUrl())
               .permitAll()
               .and()
               .logout()
               .logoutSuccessHandler(myLogoutSuccessHandler)
               .permitAll()
               .and()
               .exceptionHandling()
               //自定义认证失败处理器
               .authenticationEntryPoint(new MyLoginUrlAuthenticationEntryPoint(securityProperties.getLoginPage()));



    }

    @Override
    public void configure(WebSecurity web) throws Exception{
        String[] ignoringArray = securityProperties.getIgnoringArray();
        if(swaggerEnable && !StringUtils.isBlank(SwaggerConfig.ACCESS_PREFIX)){
            ignoringArray = ArrayUtils.addAll(ignoringArray,SwaggerConfig.ACCESS_PREFIX.split(","));
        }
        web.ignoring().antMatchers(ignoringArray);
    }

    /**
     * 声明密码加密方式
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception{
        auth.userDetailsService(myUserDetailService).passwordEncoder(passwordEncoder());
                // 配置密码加密方式,也可以不指定,默认就是BCryptPasswordEncoder
    }

}

验证成功处理器

https://www.cnblogs.com/deviltofree/p/10044046.html(登录失败异常处理)

package com.students.springbootstu.security.handler;

import com.students.springbootstu.common.ResponseResult;
import com.students.springbootstu.util.RequestUtil;
import com.students.springbootstu.util.ResponseUtil;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
    /**
     * 登录成功处理器
     */
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws ServletException, IOException {
        // 判断是否是ajax请求,因为ajax请求不能跳转页面,所以要单独判断
        if (RequestUtil.isAjaxRequest(request)) {
            ResponseUtil.printJson(response,  ResponseResult.success());
        } else {
            response.sendRedirect("/");
        }
    }
}

用户账号权限验证类
 

package com.students.springbootstu.security.service;

import com.students.springbootstu.entity.TbStudent;
import com.students.springbootstu.entity.TbStupwd;
import com.students.springbootstu.service.TbStudentService;
import com.students.springbootstu.service.TbStupwdService;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

@Service
public class MyUserDetailService implements UserDetailsService {

    @Resource
    private TbStupwdService tbStupwdService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //username要对应html里面的表单提交name
        TbStupwd user = tbStupwdService.queryByStuAccount(username);

        if(user == null){
            throw new UsernameNotFoundException("userName not found");
        }
        //创建一个集合来放置权限
        Collection<GrantedAuthority> authorities = getAuthorities(user);
        //实例化一个userDetails对象
        UserDetails userDetails = new User(username, user.getStupwd(), true,true,true,true, authorities);

        return userDetails;
    }

    private Collection<GrantedAuthority> getAuthorities(TbStupwd user){
        List<GrantedAuthority> authList = new ArrayList<GrantedAuthority>();
        //注意:这里每个权限前面都要加ROLE_。否在最后验证不会通过
        authList.add(new SimpleGrantedAuthority("ROLE_"+user.getRole()));
        return authList;
    }

}

thymleaf+bootstrap模板

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>login</title>
    <meta content="text/html;charset=UTF-8"/>
    <link th:href="@{bootstrap/css/bootstrap.css}" rel="stylesheet" >
    <link th:href="@{css/login.css}" rel="stylesheet">
    <script th:src="@{scripts/jQuery3.15min.js}" type="text/javascript"></script>
    <!-- 加载 Bootstrap 的所有 JavaScript 插件。你也可以根据需要只加载单个插件。 -->
    <script  th:src="@{bootstrap/js/bootstrap.js}" type="text/javascript"></script>
</head>
<body>
<!--nav-->
<div th:replace="common::topbar"></div>
<!-- body -->
<div class="container">
    <div class="text-center">
        <form class="form-signin" th:action="@{/login}" method="post">
            <img class="mb-4"  alt="" width="72" height="72"  th:src="@{images/bootstrap-solid.svg}">
            <h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
            <label for="username" class="sr-only" >Email address</label>
            <input  id="username" class="form-control" name="username" placeholder="account" required autofocus>
            <label for="password" class="sr-only">Password</label>
            <input type="password" id="password" class="form-control" placeholder="Password" name="password" required>
            <div class="checkbox mb-3">
                <label>
                    <input type="checkbox" value="remember-me"> Remember me
                </label>
            </div>
            <button class="btn btn-lg btn-primary btn-block" type="submit" >Sign in</button>
            <p class="mt-5 mb-3 text-muted">&copy; 2017-2020</p>
        </form>
    </div>

</div>
<!--footer-->
<div th:replace="common::bottombar"></div>


</body>
</html>

其他配置(session)

https://www.jianshu.com/p/caa1d35d3524

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值