Spring Security自定义登录验证及登录返回结果

Spring Security自定义登录验证及登录返回结果

  • 一.功能描述
  • 二.处理逻辑
    • 简单流程
    • 自定义UserDetails
    • 自定义UserDetailsDAO
    • 自定义UserDetailsService
    • 自定义用户登录验证逻辑AuthenticationProvider
  • 三.Spring Security基本配置信息
  • 四.登录登出自定义处理器
    • 登录成功处理器LocalAuthenticationSuccessHandler
    • 登录失败处理器LocalAuthenticationFailureHandler
    • 登出成功处理器LocalLogoutSuccessHandler
    • 匿名用户访问无权限处理器LocalAuthenticationEntryPoint
    • 认证用户访问无权限处理器LocalAccessDeniedHandler
    • 旧用户被踢处理器LocalSessionInformationExpiredStrategy

一.功能描述

使用Spring Boot + Spring Security实现自定义登录验证及登录登出返回结果. 
适用于前后端分离开发的项目.

如有不妥之处, 请各位大神指教.

二.处理逻辑

简单流程

验证成功
验证失败
Request登录
Spring Security
自定义LocalAuthenticationProvider
自定义LocalAuthenticationSuccessHandler
自定义LocalAuthenticationFailureHandler
验证成功
Request登出
Spring Security
自定义LocalLogoutSuccessHandler

自定义UserDetails

@javax.persistence.Entity
@org.hibernate.annotations.Table(appliesTo = "local_user_details")
public class LocalUserDetails implements java.io.Serializable, UserDetails {
  private static final long serialVersionUID = 1594783117560L;

  @javax.persistence.Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "[id]", nullable = false, unique = true, length = 0, precision = 20, scale = 0, columnDefinition = "bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键'")
  @ApiModelProperty(value = "主键", required = false, hidden = false, example = "主键")
  @JsonInclude(value = JsonInclude.Include.NON_EMPTY)
  private Long id;

  @NonNull
  @Size(min = 3, max = 18)
  @Column(name = "[userid]", nullable = false, unique = false, length = 64, precision = 0, scale = 0, columnDefinition = "varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '用户索引'")
  @ApiModelProperty(value = "用户索引", required = true, hidden = false, example = "用户索引")
  @JsonInclude(value = JsonInclude.Include.NON_EMPTY)
  private String userid = "";

  @NonNull
  @Size(min = 3, max = 64)
  @Column(name = "[passwd]", nullable = false, unique = false, length = 128, precision = 0, scale = 0, columnDefinition = "varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT '用户密码'")
  @ApiModelProperty(value = "用户密码", required = true, hidden = false, example = "用户密码")
  @JsonInclude(value = JsonInclude.Include.NON_EMPTY)
  private String passwd = "";

  public LocalUserDetails() {}
  public Long getId() {
    return this.id;
  }
  public void setId(Long id) {
    this.id = id;
  }
  public String getUserid() {
    return this.userid;
  }
  public void setUserid(String userid) {
    this.userid = userid;
  }
  public String getPasswd() {
    return this.passwd;
  }
  public void setPasswd(String passwd) {
    this.passwd = passwd;
  }
  @Override
  public Collection<? extends GrantedAuthority> getAuthorities() {
    //返回分配给用户的角色列表
    return new ArrayList<>();
  }
  @Override
  public String getPassword() {
    return this.getPasswd();
  }
  @Override
  public String getUsername() {
    return this.getUserid();
  }
  @Override
  public boolean isAccountNonExpired() {
    // 指定账户是否未过期.
    return true;
  }
  @Override
  public boolean isAccountNonLocked() {
    // 指定账户是否未锁定.
    return true;
  }
  @Override
  public boolean isCredentialsNonExpired() {
    // 指定密码是否未过期.
    return true;
  }
  @Override
  public boolean isEnabled() {
    // 指定用户是否已启用, 禁用的用户无法进行身份验证.
    return true;
  }
}

自定义UserDetailsDAO

public interface LocalUserDetailsDAO extends JpaRepository<LocalUserDetails, Long>, JpaSpecificationExecutor<LocalUserDetails> {
  Optional<LocalUserDetails> findByUserid(String userid);
}

自定义UserDetailsService

@Service
@Transactional
public class AuthorityUserLoginInfoService implements UserDetailsService {
  @Autowired
  private LocalUserDetailsDAO dao;
  @Override
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    Optional<LocalUserDetails> user = dao.findByUserid(username);
    if (user.isPresent()) {
      if (!user.get().isEnabled()) { throw new DisabledException("暂无权限!"); }
    } else {
      throw new UsernameNotFoundException("用户名或密码不正确!");
    }
    return user.get();
  }
}

自定义用户登录验证逻辑AuthenticationProvider

@Component
public class LocalAuthenticationProvider implements AuthenticationProvider {
  @Autowired
  private AuthorityUserLoginInfoService userService;
  private final PasswordEncoder encoder = new BCryptPasswordEncoder();
  /** 自定义验证方式 */
  @Override
  public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    String username = authentication.getName();
    String password = (String) authentication.getCredentials();

    // 获取Request, 获取其他参数信息
    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
    HttpSession session = attributes.getRequest().getSession();
    Object others = session.getAttribute("others");

    UserDetails node = userService.loadUserByUsername(username);
    if (!encoder.matches(password, node.getPassword())) { throw new BadCredentialsException("用户名或密码不正确!"); }
    return new UsernamePasswordAuthenticationToken(node, password, node.getAuthorities());
  }
  @Override
  public boolean supports(Class<?> authentication) {
    return true;
  }
}

三.Spring Security基本配置信息

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

  @Value(value = "${ignoringAntMatchers:/**}")
  private String ignoringAntMatchers;

  @Value(value = "${loginPage:/login}")
  private String loginPage;

  @Value(value = "${loginProcessingUrl:/api/user/login}")
  private String loginProcessingUrl;

  @Value(value = "${logoutUrl:/api/user/logout}")
  private String logoutUrl;

  @Value(value = "${expiredUrl:/login}")
  private String expiredUrl;

  @Autowired
  private DataSource dataSource;

  @Autowired
  private AuthorityUserLoginInfoService userService;

  @Bean
  public BCryptPasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder(4);
  }

  @Bean
  public AuthenticationProvider authenticationProvider() {
    return new LocalAuthenticationProvider();
  }

  @Override
  public void configure(AuthenticationManagerBuilder auth) throws Exception {
    // 自定义用户登录验证
    auth.authenticationProvider(authenticationProvider());
    // 指定密码加密所使用的加密器为passwordEncoder(), 需要将密码加密后写入数据库
    auth.userDetailsService(this.userService).passwordEncoder(passwordEncoder());
    // 不删除凭据,以便记住用户
    auth.eraseCredentials(false);
  }

  @Override
  public void configure(WebSecurity web) throws Exception {
    // 设置不拦截规则
    web.ignoring().antMatchers(this.ignoringAntMatchers.split(","));
  }

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    // 设置拦截规则
    http.authorizeRequests().anyRequest().authenticated();

    // 自定义登录信息
    http.csrf().disable().formLogin() //
        .loginPage(this.loginPage) // 自定义登录页url,默认为/login
        .loginProcessingUrl(this.loginProcessingUrl) // 登录验证地址, 即RequestMapping地址
        // 用户名的请求字段. 默认为org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_USERNAME_KEY
        .usernameParameter("username")
        // 用户名的请求字段. 默认为org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_PASSWORD_KEY
        .passwordParameter("password") // 
        .successHandler(new LocalAuthenticationSuccessHandler()) // 登录验证成功后, 执行的内容
        // .defaultSuccessUrl("/index") // 登录验证成功后, 跳转的页面, 如果自定义返回内容, 请使用successHandler方法
        // .successForwardUrl("/index") // 登录验证成功后, 跳转的页面, 如果自定义返回内容, 请使用successHandler方法
        .failureHandler(new LocalAuthenticationFailureHandler()) // 登录验证失败后, 执行的内容
        // .failureUrl("/login?error")  // 登录验证失败后, 跳转的页面, 如果自定义返回内容, 请使用failureHandler方法
        .permitAll();

    // 自定义异常处理器
    http.exceptionHandling() // 异常处理器
        // 用来解决匿名用户访问无权限资源时的异常
        .authenticationEntryPoint(new LocalAuthenticationEntryPoint())
        // 用来解决认证过的用户访问无权限资源时的异常, 跳转的页面, 如果自定义返回内容, 请使用accessDeniedHandler方法
        // .accessDeniedPage("/error")
        // 用来解决认证过的用户访问无权限资源时的异常
        .accessDeniedHandler(new LocalAccessDeniedHandler());

    // 自定义注销信息
    http.logout() // 
        .logoutUrl(this.logoutUrl) // 登出验证地址, 即RequestMapping地址
        .logoutSuccessHandler(new LocalLogoutSuccessHandler()) // 登录验证成功后, 执行的内容
        // .logoutSuccessUrl("/login?logout") // 登录验证成功后, 跳转的页面, 如果自定义返回内容, 请使用logoutSuccessHandler方法
        .deleteCookies("JSESSIONID") // 退出登录后需要删除的cookies名称
        .invalidateHttpSession(true) // 退出登录后, 会话失效
        .permitAll();

    // remember-me配置
    http.rememberMe() // 
        // org.springframework.security.config.annotation.web.configurers.RememberMeConfigurer.DEFAULT_REMEMBER_ME_NAME
        .rememberMeCookieName("remember-me")
        // org.springframework.security.config.annotation.web.configurers.RememberMeConfigurer.DEFAULT_REMEMBER_ME_NAME
        .rememberMeParameter("remember-me")
        // org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices.TWO_WEEKS_S
        .tokenValiditySeconds(360000)
        // tokenRepository
        .tokenRepository(new LocalPersistentTokenRepository(this.dataSource));

    // session管理
    http.sessionManagement().sessionFixation().changeSessionId() //
        .maximumSessions(1) // 最大会话数
        .maxSessionsPreventsLogin(false) // 达到最大数后, 强制验证, 踢出旧用户
        // 旧用户被踢出后, 跳转的页面, 如果自定义返回内容, 请使用expiredSessionStrategy方法
        // .expiredUrl("/login?expired")
        // 旧用户被踢出后, 执行的内容
        .expiredSessionStrategy(new LocalSessionInformationExpiredStrategy());
  }

  @Bean
  @Override
  protected AuthenticationManager authenticationManager() throws Exception {
    return super.authenticationManager();
  }

}

四.登录登出自定义处理器

登录成功处理器LocalAuthenticationSuccessHandler

public class LocalAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
  private static final Gson gson = new GsonBuilder().disableHtmlEscaping().create();

  @Override
  public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
      throws IOException, ServletException {
    response.setCharacterEncoding("utf-8");
    response.setContentType("application/json;charset=utf-8");
    response.getWriter().print(gson.toJson(new Result<>(ResultStatus.SUCCESS_LOGIN)));
  }
}

登录失败处理器LocalAuthenticationFailureHandler

public class LocalAuthenticationFailureHandler implements AuthenticationFailureHandler {
  private static final Gson gson = new GsonBuilder().disableHtmlEscaping().create();

  @Override
  public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception)
      throws IOException, ServletException {
    response.setCharacterEncoding("utf-8");
    response.setContentType("application/json;charset=utf-8");
    response.getWriter().print(gson.toJson(new Result<>(ResultStatus.ERROR_LOGIN)));
  }
}

登出成功处理器LocalLogoutSuccessHandler

public class LocalLogoutSuccessHandler implements LogoutSuccessHandler {
  private static final Gson gson = new GsonBuilder().disableHtmlEscaping().create();

  @Override
  public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
      throws IOException, ServletException {
    response.setCharacterEncoding("utf-8");
    response.setContentType("application/json;charset=utf-8");
    response.getWriter().print(gson.toJson(new Result<>(20001, "登出成功!")));
  }
}

匿名用户访问无权限处理器LocalAuthenticationEntryPoint

public class LocalAuthenticationEntryPoint implements AuthenticationEntryPoint {
  private static final Gson gson = new GsonBuilder().disableHtmlEscaping().create();

  @Override
  public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
      throws IOException, ServletException {
    response.setCharacterEncoding("utf-8");
    response.setContentType("application/json;charset=utf-8");
    response.getWriter().print(gson.toJson(new Result<>(ResultStatus.WRONG_DEAL)));
  }
}

认证用户访问无权限处理器LocalAccessDeniedHandler

public class LocalAccessDeniedHandler implements AccessDeniedHandler {
  private static final Gson gson = new GsonBuilder().disableHtmlEscaping().create();

  @Override
  public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException)
      throws IOException, ServletException {
    response.setCharacterEncoding("utf-8");
    response.setContentType("application/json;charset=utf-8");
    response.getWriter().print(gson.toJson(new Result<>(ResultStatus.WRONG_DEAL)));
  }
}

旧用户被踢处理器LocalSessionInformationExpiredStrategy

public class LocalSessionInformationExpiredStrategy implements SessionInformationExpiredStrategy {
  private static final Gson gson = new GsonBuilder().disableHtmlEscaping().create();

  @Override
  public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
    HttpServletResponse response = event.getResponse();
    response.setCharacterEncoding("utf-8");
    response.setContentType("application/json;charset=utf-8");
    response.getWriter().print(gson.toJson(new Result<>(20002, "用户已退出!")));
  }
}

你可能感兴趣的:(JPA,Java,Spring,Security)