• SpringSecurity自定义验证登录


    一、描述

    使用springboot+spring security实现自定义登录验证及登录登出结果返回
    适用于前后端分离

    二、处理逻辑

    简单流程

    自定义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, "用户已退出!")));
      }
    }
    

    参考文章:https://blog.csdn.net/dahaiaixiaohai/article/details/107375939

  • 相关阅读:
    VIPServer VS LVS
    阿里中间件
    每天进步一点点——Linux
    在线制图工具!!!
    test
    RHCE 基础学习
    TCP/IP源码(59)——TCP中的三个接收队列
    多队列网卡简介以及Linux通过网卡发送数据包源码解读
    Queueing in the Linux Network Stack !!!!!!!!!!!!!!!
    css选择器
  • 原文地址:https://www.cnblogs.com/2393920029-qq/p/15498965.html
Copyright © 2020-2023  润新知