springboot2.1.1+spring security 不同平台通过不同请求路径实现登录

问题描述

某项目后台管理,既管理后台账号,又管理前台账号,前台账号信息存储front_account, 后台账号信息存储back_account。这里通过spring security 既管理后台账号的认证,也管理前台账号的认证。

分析问题

既然是两张表,而且为了适应不同环境,那么首先我们知道,单个项目内既要实现前台登录,又要实现后台登录,那么前后台的登录路径肯定不同,基于路径的区分,我们需要关心两点:
1.security是否对路径做拦截
2.重写loadUserByUsername(String username)方法

解决方案

路径拦截在UsernamePasswordAuthenticationFilter中可以看到new AntPathRequestMatcher("/login", “POST”),这段代码就是过滤,所以重写它
loadUserByUsername authenticate#retrieveUser 里面有调用到loadUserByUsername,所以重写它

代码实现

基本套路为:
config 继承WebSecurityConfigurerAdapter,重写configure(HttpSecurity http)
Filter 继承AbstractAuthenticationProcessingFilter,参考UsernamePasswordAuthenticationFilter 实现路径过滤和attemptAuthentication(身份验证)
token 继承AbstractAuthenticationToken, 参考UsernamePasswordAuthenticationToken
provider 实现AuthenticationProvider,参考DaoAuthenticationProvider 重写authenticate和retrieveUser方法

Config


@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	
	@Override
    protected void configure(HttpSecurity http) throws Exception {
    ...//TODO 主要是下面两个
    	//后台过滤
        http.addFilterAt(backAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
        //前台过滤
        http.addFilterAt(frontAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    }
}

@Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    BackAuthenticationFilter backAuthenticationFilter() throws Exception {
        BackAuthenticationFilter filter = new BackAuthenticationFilter();
        filter.setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() {

            @Override
            public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                                Authentication authentication) throws IOException, ServletException {
                LoginUser loginUser = (LoginUser) authentication.getPrincipal();

                Token token = tokenService.saveToken(loginUser);
                ResponseUtil.responseJson(response, HttpStatus.OK.value(), token);
            }
        });
        filter.setAuthenticationFailureHandler(new AuthenticationFailureHandler() {

            @Override
            public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                                AuthenticationException exception) throws IOException, ServletException {
                String msg = null;
                if (exception instanceof BadCredentialsException) {
                    msg = "密码错误";
                } else {
                    msg = exception.getMessage();
                }

                ResponseUtil.responseJson(response, HttpStatus.UNAUTHORIZED.value(), Result.error(HttpStatus.UNAUTHORIZED.value(), msg));
            }
        });
        filter.setAuthenticationManager(authenticationManagerBean());
        return filter;
    }

BackAuthenticationFilter

public class BackAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    // ~ Static fields/initializers
    // =====================================================================================

    public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
    public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";

    private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
    private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
    private boolean postOnly = true;
   
    // ~ Constructors
    // ===================================================================================================

    public BackAuthenticationFilter() {
        //路径过滤
        super(new AntPathRequestMatcher("/back/login", "POST"));
    }

    // ~ Methods
    // ========================================================================================================

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        if (postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException(
                    "Authentication method not supported: " + request.getMethod());
        }


        String username = obtainUsername(request);
        String password = obtainPassword(request);

        if (username == null) {
            username = "";
        }

        if (password == null) {
            password = "";
        }

        username = username.trim();

        AbstractAuthenticationToken authRequest = new BackAuthenticationToken(
                username, password);

        // Allow subclasses to set the "details" property
        setDetails(request, authRequest);

        return this.getAuthenticationManager().authenticate(authRequest);
    }

    protected String obtainPassword(HttpServletRequest request) {
        return request.getParameter(passwordParameter);
    }

    protected String obtainUsername(HttpServletRequest request) {
        return request.getParameter(usernameParameter);
    }

    protected void setDetails(HttpServletRequest request,
                              AbstractAuthenticationToken authRequest) {
        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
    }

    public void setUsernameParameter(String usernameParameter) {
        Assert.hasText(usernameParameter, "Username parameter must not be empty or null");
        this.usernameParameter = usernameParameter;
    }

    public void setPasswordParameter(String passwordParameter) {
        Assert.hasText(passwordParameter, "Password parameter must not be empty or null");
        this.passwordParameter = passwordParameter;
    }

    public void setPostOnly(boolean postOnly) {
        this.postOnly = postOnly;
    }

    public final String getUsernameParameter() {
        return usernameParameter;
    }

    public final String getPasswordParameter() {
        return passwordParameter;
    }


}

BackAuthenticationToken

public class BackAuthenticationToken extends AbstractAuthenticationToken {
    private final Object principal;
    private String credentials;

    public BackAuthenticationToken(Object principal, String credentials) {
        super(null);
        this.principal = principal;
        this.credentials = credentials;
        setAuthenticated(false);
    }

    // ~ Methods
    // ========================================================================================================

    public String getCredentials() {
        return this.credentials;
    }

    public Object getPrincipal() {
        return this.principal;
    }

    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
        if (isAuthenticated) {
            throw new IllegalArgumentException(
                    "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
        }

        super.setAuthenticated(false);
    }

    @Override
    public void eraseCredentials() {
        super.eraseCredentials();
        credentials = null;
    }
}

BackAuthenticationProvider

public class BackAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    @Qualifier("bCryptPasswordEncoder")
    private BCryptPasswordEncoder passwordEncoder;
    @Autowired
    private BackUserDetailsServiceImpl userDetailsService;
    /**
     * The plaintext password used to perform
     * PasswordEncoder#matches(CharSequence, String)}  on when the user is
     * not found to avoid SEC-2056.
     */
    private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
    private MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
    private UserCache userCache = new NullUserCache();
    private boolean hideUserNotFoundExceptions = true;
    /**
     * The password used to perform
     * {@link PasswordEncoder#matches(CharSequence, String)} on when the user is
     * not found to avoid SEC-2056. This is necessary, because some
     * {@link PasswordEncoder} implementations will short circuit if the password is not
     * in a valid format.
     */
    private volatile String userNotFoundEncodedPassword;

    public UserCache getUserCache() {
        return userCache;
    }

    public void setUserCache(UserCache userCache) {
        this.userCache = userCache;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();
        String password = (String) authentication.getCredentials();
        if (StringUtils.isEmpty(password)) {
            throw new BadCredentialsException("密码不能为空");
        }

        boolean cacheWasUsed = true;
        UserDetails user = this.userCache.getUserFromCache(username);
        if (user == null) {
            cacheWasUsed = false;

            try {
                user = retrieveUser(username,
                        (BackAuthenticationToken) authentication);
            } catch (UsernameNotFoundException notFound) {
                log.debug("User '" + username + "' not found");

                if (hideUserNotFoundExceptions) {
                    throw new BadCredentialsException(messages.getMessage(
                            "AbstractUserDetailsAuthenticationProvider.badCredentials",
                            "Bad credentials"));
                } else {
                    throw notFound;
                }
            }

            Assert.notNull(user,
                    "retrieveUser returned null - a violation of the interface contract");
        }


        if (null == user) {
            throw new BadCredentialsException("用户不存在");
        }

        if (!passwordEncoder.matches(password, user.getPassword())) {
            throw new BadCredentialsException("用户名或密码不正确");
        }
        if (!cacheWasUsed) {
            this.userCache.putUserInCache(user);
        }

        UsernamePasswordAuthenticationToken result =
                new UsernamePasswordAuthenticationToken(user, password, user.getAuthorities());
        result.setDetails(authentication.getDetails());
        return result;
    }

    private UserDetails retrieveUser(String username, BackAuthenticationToken authentication) throws AuthenticationException {
        prepareTimingAttackProtection();
        try {
            UserDetails loadedUser = this.userDetailsService.loadUserByUsername(username);
            if (loadedUser == null) {
                throw new InternalAuthenticationServiceException(
                        "UserDetailsService returned null, which is an interface contract violation");
            }
            return loadedUser;
        } catch (UsernameNotFoundException ex) {
            mitigateAgainstTimingAttack(authentication);
            throw ex;
        } catch (InternalAuthenticationServiceException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
        }
    }

    private void prepareTimingAttackProtection() {
        if (this.userNotFoundEncodedPassword == null) {
            this.userNotFoundEncodedPassword = this.passwordEncoder.encode(USER_NOT_FOUND_PASSWORD);
        }
    }

    private void mitigateAgainstTimingAttack(BackAuthenticationToken authentication) {
        if (authentication.getCredentials() != null) {
            String presentedPassword = authentication.getCredentials();
            this.passwordEncoder.matches(presentedPassword, this.userNotFoundEncodedPassword);
        }
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return (BackAuthenticationToken.class.isAssignableFrom(authentication));
    }


}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot 2.1.1 版本中,使用 spring-elasticsearch 时出现 "No qualifying bean" 错误通常是由于以下原因导致的: 1. 缺少必要的依赖:请确保您的项目中已经正确添加了 spring-elasticsearch 的依赖。在 Maven 或 Gradle 构建文件中检查依赖是否正确引入。 2. 没有正确配置 Elasticsearch 相关的属性:在您的应用程序配置文件(例如 application.properties 或 application.yml)中,确保已经配置了正确的 Elasticsearch 连接属性,例如主机名、端口号等。还要确保您的配置文件位于正确的位置,并且被正确加载。 3. 缺少对 ElasticsearchRepository 的实现类:请确保您的项目中存在一个继承自 ElasticsearchRepository 的接口,并且有一个具体的实现类。实现类需要添加 `@Repository` 注解,以便被 Spring 自动发现并注入。 4. 包扫描问题:如果您的实现类不在 Spring 扫描的包路径下,Spring 可能无法自动发现该类。请确保您的实现类在正确的包路径下,并且包扫描配置正确。 5. 版本兼容性问题:请确保您使用的 spring-elasticsearch 版本与 Spring Boot 2.1.1 兼容。有时候特定版本的库可能不兼容,建议查看官方文档或社区支持来确认版本兼容性。 请检查以上可能导致错误的原因,并逐一排除。如果问题仍然存在,请提供更多的错误信息、配置以及依赖信息,以便更详细地帮助您解决问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值