【安全篇】Spring Boot 整合 Spring Security 安全框架

安全框架

安全框架,简单说是对访问权限进行控制,应用的安全性包括用户认证(Authentication)用户授权(Authorization)两个部分。

  • 用户认证:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码,系统通过校验用户名和密码来完成认证过程。
  • 用户授权:验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。

一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。

Apache Shiro

Apache Shiro 是一个强大易用的 Java 安全框架,提供了认证、授权、加密和会话管理等功能,对于任何一个应用程序,Shiro 都可以提供全面的安全管理服务。并且相对于其他安全框架,Shiro 要简单的多。

Spring Security

Spring Security 是一个强大的可高度定制的认证和授权框架,对于 Spring 应用来说它是一套 Web 安全标准。Spring Security 注重于为 Java 应用提供认证和授权功能,像所有的Spring项目一样,它对自定义需求具有强大的扩展性。

小结

Apache Shiro 因为它相当简单,对比 Spring Security,可能没有 Spring Security 做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的 Shiro 就足够了。合适的才是最好的!

Spring Boot 整合 Spring Security

1.添加依赖


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-securityartifactId>
dependency>

2.启动项目

启动日志增加了如下内容,通过该内容可以找到默认用户名密码

  • 用户名:UserDetailsServiceAutoConfiguration 类 -> User 类 -> private String name = “user”;
  • 密码:5b7773d7-1c0c-432f-9363-de1bd8fe6166
2022-04-22 14:15:46.762  WARN 8284 --- [           main] .s.s.UserDetailsServiceAutoConfiguration : 

Using generated security password: 5b7773d7-1c0c-432f-9363-de1bd8fe6166

This generated password is for development use only. Your security configuration must be updated before running your application in production.

3.登录

访问:http://127.0.0.1:8080/user/1 接口,跳转到:http://127.0.0.1:8080/login 登录页面(security 默认登录页面),输入用户名、密码即可跳转回接口

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fhF0abLG-1650617709540)(O:\appCache\Typora\img\【安全篇】Spring Boot 整合 Spring Security 安全框架\image-20220422142048281.png)]

登录后的用户信息默认在 Cookies

Name Value
JSESSIONID A58341FC3E4BD7A71700C4F65FF26AF3

Spring Security 定制化登录

模拟从数据库读取用户名密码,Demo 地址:mingyue-springboot-security

1.自定义登录认证逻辑

  • MingYueUserDetailsService

    用户登录的逻辑处理处

    import cn.hutool.core.util.ObjectUtil;
    import cn.hutool.core.util.StrUtil;
    import com.csp.mingyue.security.model.MingYueUser;
    import com.csp.mingyue.security.vo.LoginUserVo;
    import lombok.RequiredArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    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.Component;
    
    /**
     * @author Strive
     * @date 2022/4/22 14:53
     * @description
     */
    @Slf4j
    @Component
    @RequiredArgsConstructor
    public class MingYueUserDetailsService implements UserDetailsService {
    
      private final MingYueUserService mingYueUserService;
    
      @Override
      public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        if (StrUtil.isBlank(username)) {
          log.info("登录用户:{} 不存在", username);
          throw new UsernameNotFoundException("登录用户:" + username + " 不存在");
        }
    
        // 查出密码
        MingYueUser user = mingYueUserService.queryUserByName(username);
        if (ObjectUtil.isNull(user)) {
          log.info("登录用户:{} 不存在", username);
          throw new UsernameNotFoundException("登录用户:" + username + " 不存在");
        }
    
        return new LoginUserVo(user);
      }
    }
    
  • MingYueUser

    import lombok.AllArgsConstructor;
    import lombok.Builder;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import lombok.ToString;
    
    /** @author Strive */
    @Data
    @ToString
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public class MingYueUser {
      private Long userId;
      private String username;
      private String password;
    }
    
  • LoginUserVo

    import com.csp.mingyue.security.model.MingYueUser;
    import java.util.Collection;
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import lombok.ToString;
    import org.springframework.security.core.CredentialsContainer;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    
    /**
     * 登录用户信息
     *
     * @author Strive
     * @date 2022/4/22 14:59
     * @description
     */
    @Data
    @ToString
    @NoArgsConstructor
    @AllArgsConstructor
    public class LoginUserVo implements UserDetails, CredentialsContainer {
    
      /** 用户 */
      private MingYueUser mingYueUser;
    
      @Override
      public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
      }
    
      @Override
      public String getPassword() {
        return new BCryptPasswordEncoder().encode(mingYueUser.getPassword());
      }
    
      @Override
      public String getUsername() {
        return mingYueUser.getUsername();
      }
    
      /** 账户是否未过期,过期无法验证 */
      @Override
      public boolean isAccountNonExpired() {
        // true:未过期
        return true;
      }
    
      /**
       * 指定用户是否解锁,锁定的用户无法进行身份验证
       *
       * 

    密码锁定 */ @Override public boolean isAccountNonLocked() { // true:未锁定 return true; } /** 指示是否已过期的用户的凭据(密码),过期的凭据防止认证 */ @Override public boolean isCredentialsNonExpired() { // true:未过期 return true; } /** 用户是否被启用或禁用。禁用的用户无法进行身份验证。 */ @Override public boolean isEnabled() { // true:未禁用 return true; } /** 认证完成后,擦除密码 */ @Override public void eraseCredentials() { mingYueUser.setPassword(null); } }

  • MingYueUserService

    import com.csp.mingyue.security.model.MingYueUser;
    import org.springframework.stereotype.Service;
    
    /** @author Strive */
    @Service
    public class MingYueUserService {
    
      /**
       * 根据用户名查询用户信息
       *
       * @param username 用户名
       * @return 用户信息
       */
      public MingYueUser queryUserByName(String username) {
        return MingYueUser.builder().userId(1L).username(username).password("123456").build();
      }
    }
    

2.登录成功的处理

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

/**
 * @author Strive
 * @date 2022/4/22 15:07
 * @description
 */
@Slf4j
@Component
public class LoginFailureHandler implements AuthenticationFailureHandler {
  @Override
  public void onAuthenticationFailure(
      HttpServletRequest request, HttpServletResponse response, AuthenticationException e)
      throws IOException, ServletException {
    log.info("login error");
    if (e instanceof AccountExpiredException) {
      // 账号过期
      log.info("[登录失败] - 用户账号过期");
    } else if (e instanceof BadCredentialsException) {
      // 密码错误
      log.info("[登录失败] - 用户密码错误");
    } else if (e instanceof CredentialsExpiredException) {
      // 密码过期
      log.info("[登录失败] - 用户密码过期");
    } else if (e instanceof DisabledException) {
      // 用户被禁用
      log.info("[登录失败] - 用户被禁用");
    } else if (e instanceof LockedException) {
      // 用户被锁定
      log.info("[登录失败] - 用户被锁定");
    } else {
      // 其他错误
      log.error(String.format("[登录失败] - [%s]其他错误"), e);
    }
  }
}

3.登录失败的处理

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

/**
 * @author Strive
 * @date 2022/4/22 15:05
 * @description
 */
@Slf4j
@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
  @Override
  public void onAuthenticationSuccess(
      HttpServletRequest request, HttpServletResponse response, Authentication authentication)
      throws IOException, ServletException {
    log.info("login success");
  }
}

4.配置 Spring Security

import com.csp.mingyue.security.handler.LoginFailureHandler;
import com.csp.mingyue.security.handler.LoginSuccessHandler;
import com.csp.mingyue.security.service.MingYueUserDetailsService;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
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;

/**
 * Spring Security 配置
 *
 * @author Strive
 * @date 2022/4/22 15:11
 * @description
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@AllArgsConstructor
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

  private final MingYueUserDetailsService mingYueUserDetailsService;

  /** 登录成功的处理 */
  private final LoginSuccessHandler loginSuccessHandler;
  /** 登录失败的处理 */
  private final LoginFailureHandler loginFailureHandler;

  /** 配置认证方式等 */
  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(mingYueUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());
  }

  /** http相关的配置,包括登入登出、异常处理、会话管理等 */
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.cors().and().csrf().disable();
    http.authorizeRequests()
        // 放行接口
        // .antMatchers().permitAll()
        // 除上面外的所有请求全部需要鉴权认证
        .anyRequest()
        .authenticated()
        // 登入
        .and()
        .formLogin()
        // 允许所有用户
        .permitAll()
        // 登录成功处理逻辑
        .successHandler(loginSuccessHandler)
        // 登录失败处理逻辑
        .failureHandler(loginFailureHandler);
  }
}

5.登录测试

地址 用户名 密码
http://127.0.0.1:8080/login mingyue 123456

补充说明

Spring Security 中文文档:

  • https://www.docs4dev.com/docs/zh/spring-security/5.1.2.RELEASE/reference/
  • https://docs.gitcode.net/spring/guide/spring-security/overview.html#%E5%BC%80%E5%A7%8B
接口类 说明
AuthenticationEntryPoint 实现 AuthenticationEntryPoint 接口,可以处理匿名用户访问无权限资源时的异常
UserDetailsService 用户登录认证逻辑
AuthenticationSuccessHandler 登录成功的处理
AuthenticationFailureHandler 登录失败的处理
LogoutSuccessHandler 退出登录的回调
InvalidSessionStrategy 登录超时的处理
SessionInformationExpiredStrategy 同一账号同时登录的用户数受限的处理
AccessDeniedHandler 登录用户没有权限访问的处理
HttpSessionIdResolver 自定义 Session 解析器

你可能感兴趣的:(Spring,全家桶,spring,boot,Spring,Security,Security,安全框架)