Spring Security同时支持用户名密码登录和OAuth登录

前置

先基于Spring Boot实现OAuth登录

Spring Security OAuth第三方登录实例

Spring Security配置

认证配置

配置两个SecurityFilterChain的bean,分别负责OAuth登录和FormLogin的认证设置。两个SecurityFilterChain的优先级通过@Order指定,优先级较高且符合匹配规则的负责处理此次请求。此处oauthFilterChain优先级较高,不以/formLogin开头的请求符合匹配规则,都由oauthFilterChain负责处理。以/formLogin开头的请求则落到formFilterChain处理。

SecurityConfiguration.java

@Bean  
@Order(2)  
public SecurityFilterChain formFilterChain(HttpSecurity http) throws Exception {  
    http.authorizeRequests((requests) -> requests.anyRequest().authenticated()).formLogin().loginProcessingUrl("/formLogin")  
        .successHandler((request, response, authentication) -> {  
            User principal = (User) authentication.getPrincipal();  
            String nickname = principal.getNickname();  
            ResponseEntity responseEntity = new ResponseEntity();  
            responseEntity.setData(nickname);  
            LanguageTrainerUtil.writeJsonToResponse(response, responseEntity, HttpServletResponse.SC_OK);  
        })  
        .failureHandler(((request, response, exception) -> {  
            ResponseEntity responseEntity = new ResponseEntity();  
            responseEntity.setMessage(exception.getMessage());  
            LanguageTrainerUtil.writeJsonToResponse(response, responseEntity, HttpServletResponse.SC_UNAUTHORIZED);  
        })).and().csrf().disable();  
    return http.build();  
}  
  
@Bean  
@Order(1)  
public SecurityFilterChain oauthFilterChain(HttpSecurity http) throws Exception {  
    // 只验证任何不以/formLogin开头的请求
    http.regexMatcher("^(?!/formLogin).*$").authorizeRequests((requests) -> requests.anyRequest().authenticated())  
        // OAuth登录相关配置
        .oauth2Login()  
        // 获取用户信息处理
        .userInfoEndpoint().userService(userRequest -> {  
            DefaultOAuth2UserService defaultOAuth2UserService = new DefaultOAuth2UserService();  
            OAuth2User oAuth2User = defaultOAuth2UserService.loadUser(userRequest);  
            checkOauthRequiredAttribute(oAuth2User, userRequest);  
            String oauthUsername = oAuth2User.getName();  
            String oauthType = userRequest.getClientRegistration().getRegistrationId();  
            Map<String, Object> oauthAttributes = oAuth2User.getAttributes();  
            User user;  
            if (checkOauthFirstLogin(oauthUsername, oauthType)) {  
                user = registerUserFromOauth(oauthUsername, oauthType, oauthAttributes);  
            } else {  
                user = userService.getUserByOauth(oauthUsername, oauthType);  
            }  
            return user;  
        })  
        .and() 
        // 登录成功处理 
        .successHandler((request, response, authentication) -> {  
            User principal = (User) authentication.getPrincipal();  
            String nickname = principal.getNickname();  
            ResponseEntity responseEntity = new ResponseEntity();  
            responseEntity.setData(nickname);  
            LanguageTrainerUtil.writeJsonToResponse(response, responseEntity, HttpServletResponse.SC_OK);  
        })  
        // 登录失败处理
        .failureHandler(((request, response, exception) -> {  
            ResponseEntity responseEntity = new ResponseEntity();  
            responseEntity.setMessage(exception.getMessage());  
            LanguageTrainerUtil.writeJsonToResponse(response, responseEntity, HttpServletResponse.SC_UNAUTHORIZED);  
        }))  
        // 异常处理,包括登录失败
        .and().exceptionHandling().authenticationEntryPoint((request, response, authException) -> {  
            ResponseEntity responseEntity = new ResponseEntity();  
            responseEntity.setMessage(authException.getMessage());  
            LanguageTrainerUtil.writeJsonToResponse(response, responseEntity, HttpServletResponse.SC_UNAUTHORIZED);  
        }).and().csrf().disable();  
    return http.build();  
}

自定义用户信息服务

用于FormLogin认证模式的用户信息获取

@Bean  
public UserDetailsService userDetailsService() {  
    return username -> {  
        User user = userService.getUserByUsername(username);  
        if (user == null) {  
            throw new UsernameNotFoundException("User:" + username + " not found");  
        }  
        return user;  
    };  
}

用户信息

实体类

User类既是实体类,也同时实现UserDetailsOAuth2User接口,分别用于FormLogin和OAuth两种认证模式,且作为Spring Security存放用户Pricipal信息的类型

User.java

@Data  
@NoArgsConstructor  
public class User implements UserDetails, OAuth2User {  
    @TableId(type = IdType.AUTO)  
    private Integer id;  
    private String username;  
    private String password;  
    private String nickname;  
  
    public User(String username, String nickname) {  
        this.username = username;  
        this.nickname = nickname;  
    }  
  
    @Override  
    public Collection<? extends GrantedAuthority> getAuthorities() {  
        return null;  
    }  
  
    @Override  
    public boolean isAccountNonExpired() {  
        return true;  
    }  
  
    @Override  
    public boolean isAccountNonLocked() {  
        return true;  
    }  
  
    @Override  
    public boolean isCredentialsNonExpired() {  
        return true;  
    }  
  
    @Override  
    public boolean isEnabled() {  
        return true;  
    }  
  
    @Override  
    public Map<String, Object> getAttributes() {  
        return null;  
    }  
  
    @Override  
    public String getName() {  
        return this.username;  
    }  
}

参考文档