SpringSecurity执行流程分析与使用

SpringSecurity执行流程分析与使用

一、SpringSecurity过滤器链

  • UsernamePasswordAuthenticationFilter:用户认证相关
  • ExceptionTranslationFilter:异常处理相关
  • FilterSecurityInterceptor:用户授权相关

SpringSecurity内置核心过滤器

二、核心过滤器之 UsernamePasswordAuthenticationFilter

此过滤器主要完成用户认证功能。认证流程如下:

1. 调用 attemptAuthentication方法

​ 进行一些前置处理(获取用户输入的表单对象,并封装为UsernamePasswordAuthenticationToken对象,也就是Authentication接口实现类对象)。

在这里插入图片描述

2. 调用authenticate方法

核心方法,进入到ProviderManager类中开始进行用户认证

在这里插入图片描述
在这里插入图片描述

3. 调用retrieveUser方法

​ 进入AbstractUserDetailsAuthenticationProvider类中,检索用户(判断表单用户是否合法)

在这里插入图片描述

4. 调用loadUserByUsername方法

​ 进入DaoAuthenticationProvider类中,调用UserDetailsService接口实现类中的loadUserByUsername方法。因此我们只需要实现UserDetailsService接口,在loadUserByUsername方法中写入判断逻辑,就可以通过用户表单输入的用户名来判断该用户是否合法。

在这里插入图片描述

5. 实现UserDetailsService接口

​ 开发人员实现此接口,返回系统用户对象(也就是数据库查询出来的用户信息,包括权限信息也可以放入UserDetails接口实现类对象里)

在这里插入图片描述

6. 调用additionalAuthenticationChecks方法

​ 回到AbstractUserDetailsAuthenticationProvider类中,调用additionalAuthenticationChecks方法,进行密码校验

在这里插入图片描述
在这里插入图片描述

7. 调用createSuccessAuthentication方法

​ 密码校验通过之后。调用createSuccessAuthentication方法,将从数据库中查询得到的该用户的权限信息,赋予表单用户对象

在这里插入图片描述

流程总结

1、首先用户输入用户名和密码进行登录,UsernamePasswordAuthenticationFilter过滤器就会将用户输入的表单对象封装成authentication接口实现类对象,然后调用authenticate方法对这个对象进行用户认证; ====> 开始认证

2、authenticate方法最终会调用UserDetailsService中的loadUserByName方法。因此,我们开发人员需要手动编写UserDetailsService的接口实现类对象,重写它的loadUserByName方法,在方法体中进行查询判断,判断用户在表单中输入的用户名是否在数据库中存在(也就是是否为合法用户)。如果是合法用户,将查询得到的正确的用户信息封装为UserDetails进行返回; 如果不是合法用户,说明用户名输入错误,直接抛出异常; ====> 认证阶段一

3、用户输入的用户名如果为合法的用户名,那么框架内部会自动调用additionalAuthenticationChecks方法,进行密码校验,校验用户输入的密码和系统中用户的正确密码是否一致。如果一致,将系统用户(UserDetils对象)的相关权限信息赋值给表单用户(Authentication对象),并进行返回; 如果不一致,说明密码输入错误,直接抛出异常; ====> 认证阶段二

三、项目整合SpringSecurity

虽然springsecurity提供了默认的用户名和密码以及登录页,供我们使用。但是这种不符合我们正常开发的场景,因此,我们需要自定义一些拓展。例如:不使用默认的用户名密码进行登录,而是从数据库中进行验证登录;不使用默认的登录页面等等。

1、引入依赖
// 这里使用的springboot版本为2.7.5
// gradle
implementation 'org.springframework.boot:spring-boot-starter-security:2.7.5'

// maven
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>2.7.5</version>
</dependency>
2、SecurityConfig配置类
@Configuration
public class SecurityConfig {

    /**
     * springSecurity中内置:用于密码加密。
     */
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 通过 getAuthenticationManager方法 获取 AuthenticationManager 对象,并注入ioc容器中。
     * 通过 AuthenticationManager.authenticate 方法进行登录认证。
     */
    @Autowired
    private AuthenticationConfiguration authenticationConfiguration;

    @Bean
    public AuthenticationManager getAuthentication() throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

    /**
     * 配置相关的过滤器链设置,并注入IOC容器中。
     * 老版本的写法是:继承WebSecurityConfigurerAdapter类,重写它的configure(HttpSecurity http)方法。
     */
    @Autowired
    private TokenAuthenticationFilter tokenAuthenticationFilter;
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        // 这里其他api还有很多,可以自行查看,这里只做基本演示。
        http
                // 关闭csrf防护
                .csrf().disable()
                // 调整策略,不使用session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                // 不需要认证的请求,允许匿名访问
                .antMatchers("/test/login").anonymous()
                // 其他请求都需要认证
                .anyRequest().authenticated();
//        // 自定义登录认证过滤器,在所有拦截器之前执行
        http.addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }
}
3、自定义UserDetailsService接口实现类
@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserService userService;

    /***
     * 根据账号获取系统用户信息
     * @param username:前台输入的用户名。
     * @return: org.springframework.security.core.userdetails.UserDetails
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 从数据库中取出用户信息
        User user = userService.findUserByName(username);

        // 判断用户是否存在
        if (Objects.isNull(user)){
            throw new UsernameNotFoundException("用户名不存在!");
        }
        // 将用户信息存入 securityUser 对象,此对象为UserDetails接口的实现类对象
        // 这里返回的是数据库中正确的用户信息,之后security会自行验证用户输入的密码是否正确,无需开发人员关注
        SecurityUser securityUser = new SecurityUser(user);
        return securityUser;
    }
}
4、自定义UserDetails接口实现类
@Data
@Slf4j
@NoArgsConstructor
@AllArgsConstructor
public class SecurityUser implements UserDetails {

    //当前登录用户(transient表示该属性不能被序列化)
    private transient User currentUserInfo;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null; // TODO 这里是权限相关,暂时没整合
    }

    @Override
    public String getPassword() {
        return currentUserInfo.getPassword();
    }

    @Override
    public String getUsername() {
        return currentUserInfo.getName();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}
5、编写登录接口

controller

@ApiOperation("用户登录")
    @PostMapping("/login")
    public R login(@RequestBody User user) {
        String token = loginService.login(user);
        return StringUtils.isNotEmpty(token) ? R.success().message("登录成功!").data("token", token) : R.failed().message("登录失败!");
    }

service

public interface LoginService {
    /**
     * 用户登录
     * @param user:前端表单提交过来的对象
     * @return:返回token
     */
    String login(User user);
}

serviceImpl

@Service
public class LoginServiceImpl implements LoginService {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private TokenManager tokenManager;

    @Override
    public String login(User user) {
        // 1、通过authenticate方法进行校验。
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getName(),user.getPassword());
        Authentication authentication = authenticationManager.authenticate(authenticationToken);
        if (Objects.isNull(authentication)){
            throw new RuntimeException("用户名或密码输入错误!"); // authentication对象为null表示登录失败。
        }

        // 2、校验成功后,生成JWT返回
        User successUser = ((SecurityUser) authentication.getPrincipal()).getCurrentUserInfo();
        return tokenManager.createToken(successUser.getName());
    }
}
6、自定义认证过滤器
/**
 * @author zbinyds
 * @title: TokenAuthenticationFilter
 * @projectName demo
 * @description: 认证过滤器。继承OncePerRequestFilter,用户每次请求先走这个过滤器,判断请求头中是否存在token,再走security内置的过滤器。
 * @date 2022.11.21 19:00
 */

@Component
public class TokenAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    private TokenManager tokenManager; // 工具类,用于生成/解析token。

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 将用户信息封装authentication对象
        UsernamePasswordAuthenticationToken authentication = getAuthentication(request);
        if (Objects.isNull(authentication)){
            filterChain.doFilter(request,response);
            return;
        }
        // 存入SecurityContextHolder中
        SecurityContextHolder.getContext().setAuthentication(authentication);

        // 放行
        filterChain.doFilter(request, response);
    }

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
        // 从请求头中获取token值
        String token = request.getHeader("token");
        if (!StringUtils.isEmpty(token)) {
            try {
                // 解析token得到用户名
                String userName = tokenManager.getUserNameFromToken(token);

                return new UsernamePasswordAuthenticationToken(userName, token, null); // TODO 权限没整合,所以这里返回null
            } catch (Exception e) {
                throw new RuntimeException("token不合法!");
            }
        }
        return null;
    }
}

至此,用户的认证功能就已经完成了。至于权限相关的,暂时还没有引入。

7、其他工具类

tokenManager

/**
 * @author zbinyds
 * @time 2022/09/13 19:34
 * @description:jwt工具类。用户生成token、根据token取值。
 */

@Component
@Slf4j
public class TokenManager {

    private long tokenExpiration = 15 * 60 * 1000; // token过期时间(15min有效)
    private String tokenSignKey = "zbinyds"; // jwt秘钥

    /**
     * 根据username生成token
     *
     * @param username:认证成功的用户名
     * @return:token
     */
    public String createToken(String username) {
        String token = Jwts.builder().setSubject(username)
                .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
                .signWith(SignatureAlgorithm.HS512, tokenSignKey).compressWith(CompressionCodecs.GZIP).compact();
        return token;
    }

    /**
     * 解析token。根据token获取用户名。
     *
     * @param token
     * @return
     */
    public String getUserNameFromToken(String token) throws Exception {
        String userName = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody().getSubject();
        return userName;
    }

    /**
     * 将token设置为过期状态。
     * @param token
     */
    public void removeToken(String token) {
        //jwttoken无需删除,客户端扔掉即可。
    }
    
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
回答: Spring Security执行流程可以简单描述为以下几个步骤。首先,请求进入应用程序后,会经过FilterChainProxy过滤器。\[3\]FilterChainProxy是Spring Security提供的一个特殊过滤器,它通过SecurityFilterChain对象将任务委派给Spring容器中的其他过滤器Bean对象。然后,请求会经过登录验证拦截器AuthenticationProcessingFilter。\[2\]这个拦截器负责处理用户的登录验证,包括验证用户的身份和密码等信息。接下来,请求会经过资源管理拦截器AbstractSecurityInterceptor。\[2\]这个拦截器负责对请求进行授权,判断用户是否有权限访问该资源。在执行过程中,Spring Security会利用一些组件来支持拦截器的实现,比如认证管理器AuthenticationManager和决策管理器accessDecisionManager等。最后,根据认证和授权的结果,Spring Security会决定是否允许用户访问请求的资源。总的来说,Spring Security通过一系列的过滤器和组件来实现声明式的安全访问控制功能。\[1\] #### 引用[.reference_title] - *1* *3* [Spring Security 中的执行原理流程分析](https://blog.csdn.net/weixin_63835553/article/details/122750865)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [SpringSecurity执行流程(笔记)](https://blog.csdn.net/weixin_51542566/article/details/119705963)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值