自定义【Spring Security】实现多种认证方式

自定义【Spring Security】实现多种认证方式

概述

Spring Security是一个功能强大且高度可定制的Java安全框架,用于保护基于Spring的应用程序。它重点提供认证(Authentication)和授权(Authorization),并且通过使用Spring的依赖注入(DI)特性,使得安全配置变得灵活和集中。

Spring Security的灵活性和强大功能使其成为保护Spring应用程序的首选安全解决方案。正是由于其高度可配置性,我想引入一组自定义认证器到Rdtalk来适配企业框架多认证诉求。

引言

企业框架通常需要实现多种认证方式,比如用户名密码、手机验证码、邮箱、企业微信等等。Spring Security可以通过自定义认证器AuthenticationProvider 来实现不同的认证方式。接下来咱们就来探索一下Spring Security具体如何来实现多种认证方式。

实践

最近项目上有对接企业微信需求,所以我们以用户名密码、企业微信登录两种方式来进行实践,其他一些登录方式扩展即可loginType即可。

自定义认证器AuthenticationProvider

首先我们可以通用的AuthenticationProvider,以及对应的认证信息Authentication,实际场景中这两个一般是配套使用。认证器AuthenticationProvider有一个认证方法authenticate(),我们需要实现该认证方法,认证成功之后返回认证信息Authentication。

1.CustomerAuthenticationProvider
package com.rdtalk.framework.security.provider;

import java.util.ArrayList;
import java.util.Map;

import javax.annotation.PostConstruct;

import com.rdtalk.common.exception.ServiceException;
import com.rdtalk.common.utils.MessageUtils;
import com.rdtalk.framework.security.model.CustomOAuth2AuthenticationInfo;
import com.rdtalk.framework.web.exception.BusinessException;
import com.rdtalk.framework.web.service.OAuth2AuthenService;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;


import lombok.extern.slf4j.Slf4j;

/**
 * 通用AuthenticationProvider
 *
 * @author rdtalk
 */
@Slf4j
public class CustomAuthenticationProvider implements AuthenticationProvider, ApplicationContextAware {

    private ApplicationContext context;

    private OAuth2AuthenService service;

    @PostConstruct
    public void initAuth2AuthenService() {
        service = context.getBean(OAuth2AuthenService.class);
    }

    @Override
    public Authentication authenticate(Authentication arg0) throws AuthenticationException {
        try {

            @SuppressWarnings("unchecked")
            Map<String, String> map = (Map<String, String>) arg0.getDetails();
            if (map == null) {
                throw new ServiceException(MessageUtils.message("user.auth.error"));
            }

            CustomOAuth2AuthenticationInfo authInfo = new CustomOAuth2AuthenticationInfo();
            authInfo.setAuthType(map.get("authType"));
            authInfo.setEquipinfo(map.get("equipinfo"));
            authInfo.setLoginName(map.get("username"));
            authInfo.setPassword(arg0.getCredentials() == null? "":arg0.getCredentials().toString());
            authInfo.setClientIp(map.get("clientIp"));
            authInfo.setClientId(map.get("client_id"));
            authInfo.setLoginType(map.get("loginType"));

            authInfo.setRequestParams(map);
            //initAuth2AuthenService();
            Object obj = this.service.auth(authInfo);
            UsernamePasswordAuthenticationToken o = new UsernamePasswordAuthenticationToken(obj,
                    arg0.getCredentials(), new ArrayList<>());
            o.setDetails(obj);
            return o;
        } catch (BusinessException e) {
            log.warn("authenticate faild..BusinessException:", e.getCause());
//            throw new BusinessException(ResultCode.UNAUTHORIZED_DATA.getCode(), e.getMessage());
            throw new ServiceException(e.getMessage());
        }catch (Exception e) {
            log.warn("authenticate faild..Exception:", e.getCause());
            throw new ServiceException(e.getMessage());
           // throw new BusinessException(ResultCode.UNAUTHORIZED_DATA.getCode(), e.getMessage());
        }
    }

    @Override
    public boolean supports(Class<?> arg0) {
        return true;
    }

    protected Authentication createSuccessAuthentication(Object principal, Authentication authentication) {
        if (principal == null) {
            return null;
        }
        UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal,
                authentication.getCredentials(), new ArrayList<>());
        result.setDetails(authentication.getDetails());
        return result;
    }

    @Override
    public void setApplicationContext(ApplicationContext arg0) throws BeansException {
        context = arg0;
    }

}

注意这里的OAuth2AuthenService,是实现多种认证方式的关键,认证管理器AuthenticationManager会通过authenticate方法查找当前需要使用哪一种认证方式。

2.编写OAuth2AuthenService入口实现类
package com.rdtalk.framework.web.service.impl;

import java.util.Date;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

import javax.annotation.PostConstruct;

import com.rdtalk.common.exception.ServiceException;
import com.rdtalk.common.utils.MessageUtils;
import com.rdtalk.framework.security.model.CustomOAuth2AuthenticationInfo;
import com.rdtalk.framework.web.exception.BusinessException;
import com.rdtalk.framework.web.service.OAuth2AuthenService;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
import com.google.common.collect.Maps;

import lombok.extern.slf4j.Slf4j;

@Primary
@Service
@Slf4j
public class OAuth2AuthenServiceImpl implements OAuth2AuthenService, ApplicationContextAware {

    private static String LOGIN_KEY = "LOGIN_KEY_";


    private Map<String, OAuth2AuthenService> authMap = new ConcurrentHashMap<>();

    @PostConstruct
    public void init() {
        Map<String, OAuth2AuthenService> map = applicationContext.getBeansOfType(OAuth2AuthenService.class);
        map.forEach((x, y) -> {
            String[] s = y.authloginType();
            for (String string : s) {
                authMap.put(string, y);
                log.info("authMap: key:{},class: {}", string, y.getClass().getName());
            }
        });
    }

    @Override
    public Object auth(CustomOAuth2AuthenticationInfo info) {
        Map<String, Object> loginResult = Maps.newHashMap();
        try {
            loginResult.put("loginResult", "S");

            OAuth2AuthenService s = authMap.get(info.getLoginType());

            if(Objects.isNull(s))
            {
                log.info("不支持的认证方式", "");
                throw new ServiceException(MessageUtils.message("user.auth.typerror"));
            }
            return s.auth(info);
        } catch (BusinessException e) {
            loginResult.put("loginResult", "F");
            loginResult.put("loginMsg", e.getMessage());

            throw new BusinessException("ERROR_AUTH", e.getMessage());
        }
    }

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

}

到这认证通过loginType 基本锁定是用哪种方式,假设当前loginType为WX,下面来构造微信的验证方法

3.以微信登录验证为例编写OAuth2AuthenService实现类
package com.rdtalk.framework.web.service.impl;

import com.google.common.collect.Maps;
import com.rdtalk.common.core.domain.entity.SysUser;
import com.rdtalk.common.core.domain.model.LoginUser;
import com.rdtalk.common.enums.UserStatus;
import com.rdtalk.common.exception.ServiceException;
import com.rdtalk.common.utils.MessageUtils;
import com.rdtalk.common.utils.StringUtils;
import com.rdtalk.framework.security.model.CustomOAuth2AuthenticationInfo;
import com.rdtalk.framework.web.exception.BusinessException;
import com.rdtalk.framework.web.service.OAuth2AuthenService;
import com.rdtalk.framework.web.service.SysPasswordService;
import com.rdtalk.framework.web.service.SysPermissionService;
import com.rdtalk.framework.web.service.UserDetailsServiceImpl;
import com.rdtalk.system.service.ISysUserService;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Primary;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

@Service
@Slf4j
public class OAuth2AuthenServiceImpl4WX implements OAuth2AuthenService {

    @Autowired
    private ISysUserService userService;
    @Autowired
    private SysPasswordService passwordService;
    @Autowired
    private SysPermissionService permissionService;
    @Override
    public Object auth(CustomOAuth2AuthenticationInfo info) {
        String username = info.getLoginName();
        SysUser user = userService.selectUserByUserName(username);
        if (StringUtils.isNull(user))
        {
            log.info("登录用户:{} 不存在.", username);
            throw new ServiceException(MessageUtils.message("user.not.exists"));
        }
        else if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
        {
            log.info("登录用户:{} 已被删除.", username);
            throw new ServiceException(MessageUtils.message("user.password.delete"));
        }
        else if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
        {
            log.info("登录用户:{} 已被停用.", username);
            throw new ServiceException(MessageUtils.message("user.blocked"));
        }

//        passwordService.validate(user);
        System.out.println("sadasdas");
        return createLoginUser(user);

    }

    public UserDetails createLoginUser(SysUser user)
    {
        return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));
    }

    public String[] authloginType() {
        return new String[] {"WX"};
    }


}

4.增加多类型验证

只需要cp OAuth2AuthenServiceImpl4WX即可,完成多种方式验证。

5.配置器SecurityConfig
/**
     * 定义认证管理器AuthenticationManager
     * @return
     */
    @Bean
    public AuthenticationManager authenticationManager() {
        List authenticationProviders = new ArrayList();
        authenticationProviders.add(customAuthenticationProvider());
        ProviderManager authenticationManager = new ProviderManager(authenticationProviders);
//        authenticationManager.setEraseCredentialsAfterAuthentication(false);
        return authenticationManager;

    }


    @Bean
    public CustomAuthenticationProvider customAuthenticationProvider() {
        CustomAuthenticationProvider customAuthenticationProvider = new CustomAuthenticationProvider();
        return customAuthenticationProvider;
    }

主要手动增加customAuthenticationProvider,并将customAuthenticationProvider加入到认证管理器

到这里实现多种认证方式基本就结束了。

验证

编写controller

  @GetMapping("/login_test")
    public AjaxResult login_test(String username)
    {
        logger.error("user",username);
        AjaxResult ajax = AjaxResult.success();
        // 生成令牌
        String token = loginService.login4WX(username);
        ajax.put(Constants.TOKEN, token);
        return ajax;
    }

模拟企业微信返回userid,传入登录验证方法 返回如下

到这里Spring Security实现多种认证方式就结束了,如有错误,感谢指正。

  • 8
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: Spring Security提供了多种登录方式的支持,可以使用表单登录、Basic认证、OAuth2等方式进行身份验证。如果需要自定义多种登录方式,可以按照以下步骤进行: 1. 实现自定义的AuthenticationProvider AuthenticationProvider是Spring Security的一个核心接口,用于实现身份验证逻辑。通过实现自定义的AuthenticationProvider,可以实现多种不同的身份验证方式。 例如,可以实现一个LDAPAuthenticationProvider,用于基于LDAP的身份验证,或者实现一个SmsCodeAuthenticationProvider,用于基于短信验证码的身份验证。 2. 配置多个AuthenticationProvider 在Spring Security的配置文件中,可以通过配置多个AuthenticationProvider来支持多种登录方式。例如,可以同时配置一个基于表单登录的AuthenticationProvider和一个基于OAuth2的AuthenticationProvider。 3. 实现自定义的AuthenticationFilter AuthenticationFilter是Spring Security用于处理身份验证请求的过滤器。通过实现自定义的AuthenticationFilter,可以实现多种不同的身份验证方式。 例如,可以实现一个基于短信验证码的AuthenticationFilter,用于处理短信验证码登录请求。 4. 配置多个AuthenticationFilter 在Spring Security的配置文件中,可以通过配置多个AuthenticationFilter来支持多种登录方式。例如,可以同时配置一个基于表单登录的AuthenticationFilter和一个基于短信验证码的AuthenticationFilter。 总的来说,实现多种登录方式的关键在于实现自定义的AuthenticationProvider和AuthenticationFilter,并在Spring Security的配置文件中进行配置。 ### 回答2: Spring Security 提供了多种自定义登录方式的选项。以下是一些常见的方法: 1. 自定义用户名密码登录:可以使用 Spring Security 的表单登录功能,通过配置用户名和密码的输入框,实现用户名密码登录功能。 例如,可以通过配置 `formLogin()` 方法来实现: ```java protected void configure(HttpSecurity http) throws Exception { http .formLogin() .loginPage("/login") .usernameParameter("username") .passwordParameter("password") .defaultSuccessUrl("/home") .permitAll(); } ``` 2. 自定义第三方登录:可以使用 Spring Security OAuth2 来实现第三方登录,例如使用 Facebook、Google 或 Github 等社交媒体的账号进行登录。 Spring Security OAuth2 提供了很多集成第三方认证的实例代码,可以根据具体的需求进行自定义。 3. 自定义手机号码登录:可以通过继承 Spring Security 的 `AbstractAuthenticationProcessingFilter` 类来实现自定义手机号码登录。 可以在自定义的过滤器中验证手机号码,并进行认证逻辑。 4. 自定义单点登录(SSO):可以通过集成 Spring Security 的 `AuthenticationProvider` 接口来实现自定义的单点登录认证。 可以通过实现该接口的 `authenticate()` 方法来处理单点登录的逻辑。 这些只是一些常见的自定义登录方式的示例,根据具体的需求,可以结合 Spring Security 提供的各种功能和扩展点,灵活地进行自定义实现
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值