spring security详解

Security过滤器的源码解析:https://blog.csdn.net/u013825231/article/details/81144569
token校验源码解析:https://blog.csdn.net/bluuusea/article/details/80284458

前言

目前手上几个项目用的权限认证都是Spring security,之前通过逛博看底层源码也多少研究了一下,今天就专门来整理一下这一块内容。整个配置下面都有代码块展示,直接复制即可使用。

认证流程

1、通过过滤器过滤到用户请求的接口,获取身份信息(假如有多个认证方式会配置provider的顺序)

2、一般将身份信息封装到封装成Authentication下的实现类UsernamePasswordAuthenticationToken中

3、通过AuthenticationManager 身份管理器(通过配置找到对应的provider)负责验证这个UsernamePasswordAuthenticationToken

4、认证成功后(认证逻辑一般在service中),AuthenticationManager身份管理器返回一个被填充满了信息的(包括上面提到的权限信息,身份信息,细节信息,但密码通常会被移除)Authentication实例。

5、SecurityContextHolder安全上下文容器将第2步填充了信息的UsernamePasswordAuthenticationToken,通过SecurityContextHolder.getContext().setAuthentication(…)方法,设置到其中来建立安全上下文(security context)。
在这里插入图片描述

核心组件

SecurityContextHolder

SecurityContextHolder是spring security最基本的组件。用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限等这些都被保存在SecurityContextHolder中。SecurityContextHolder默认是使用ThreadLocal实现的,这样就保证了本线程内所有的方法都可以获得SecurityContext对象。

可以通此方法过来获取当前操作用户信息:

SecurityContextHolder.getContext().getAuthentication().getPrincipal();
默认返回的对象是UserDetails实例,其中包含了username,password和权限等信息,当然,我们也可以通过实现这个接口自定义我们自己的UserDetails实例,给我们自己的应用使用,以符合需要的业务逻辑。比如下面只对token进行操作就可以吧token作为属性放入UserDetails实现类中。

Authentication

Authentication是Spring Security方式的认证主体。

<1> Authentication是spring security包中的接口,直接继承自Principal类,而Principal是位于java.security包中的。可以见得,Authentication在spring security中是最高级别的身份/认证的抽象。
<2> 由这个顶级接口,我们可以得到用户拥有的权限信息列表,密码,用户细节信息,用户身份信息,认证信息。
authentication.getPrincipal()返回了一个Object,我们将Principal强转成了Spring Security中最常用的UserDetails,这在Spring Security中非常常见,接口返回Object,使用instanceof判断类型,强转成对应的具体实现类。接口详细解读如下:

getAuthorities(),权限信息列表,默认是GrantedAuthority接口的一些实现类,通常是代表权限信息的一系列字符串。
getCredentials(),密码信息,用户输入的密码字符串,在认证过后通常会被移除,用于保障安全。
getDetails(),细节信息,web应用中的实现接口通常为 WebAuthenticationDetails,它记录了访问者的ip地址和sessionId的值。
getPrincipal(),最重要的身份信息,大部分情况下返回的是UserDetails接口的实现类,也是框架中的常用接口之一。

AuthenticationManager

AuthenticationManager(接口)是认证相关的核心接口,也是发起认证的出发点,因为在实际需求中身份认证的方式有多种,一般不使用AuthenticationManager,而是使用AuthenticationManager的实现类ProviderManager ,ProviderManager内部会维护一个List列表,存放多种认证方式,实际上这是委托者模式的应用(Delegate)。也就是说,核心的认证入口始终只有一个:AuthenticationManager,不同的认证方式对应不同的AuthenticationProvider。

总结:

SecurityContextHolder:存放身份信息的容器

Authentication:用户信息的抽象

AuthenticationManager:身份认证器

结合springboot实现对token验证

1、场景

拦截api/的所有接口进行验证,验证token用户与id用户是否一致,不一致或token过期则没有权限访问

2、实现

1、添加security相关依赖:spring-boot-starter-security spring-security-oauth2

<!--权限spring security-->
        <!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-core -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-core</artifactId>
            <version>4.2.3.RELEASE</version>
        </dependency>

        <!--权限spring security-->
        <!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-config -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>4.2.3.RELEASE</version>
        </dependency>

        <!--oauth2-->
        <!-- https://mvnrepository.com/artifact/org.springframework.security.oauth/spring-security-oauth2 -->
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>${spring-security-oauth.version}</version>
        </dependency>

2、全局配置类,根据不同需求配置不同的过滤器和provider(代码片段)

/**
 * Description: No Description
 *
 * @author ZhuZiKai
 * @date 2020/2/14 0014
 */
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private BaseUserDetailServiceImpl baseUserDetailService;

    @Autowired
    private AuthenticationSuccessHandler customAuthenticationSuccessHandler;

    @Autowired
    private AuthenticationFailureHandler customAuthenticationFailureHandler;

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

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(myAuthenticationProvider());
    }

    @Override
    public void configure(WebSecurity web) {
        web.ignoring().antMatchers("/favor.ioc", "/login", "/oauth/token");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .addFilterAt(getMyLoginAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin().loginProcessingUrl("/login").permitAll().and()
                .csrf().disable();
    }

    /**
     * 自定义登陆过滤器
     *
     * @return
     */
    @Bean
    public MyLoginAuthenticationFilter getMyLoginAuthenticationFilter() {
        MyLoginAuthenticationFilter filter = new MyLoginAuthenticationFilter();
        try {
            filter.setAuthenticationManager(this.authenticationManagerBean());
        } catch (Exception e) {
            e.printStackTrace();
        }
        filter.setAuthenticationSuccessHandler(customAuthenticationSuccessHandler);
        filter.setAuthenticationFailureHandler(customAuthenticationFailureHandler);
        return filter;
    }

    /**
     * 自定义密码验证
     *
     * @return
     */
    @Bean
    public MyAuthenticationProvider myAuthenticationProvider() {
        MyAuthenticationProvider provider = new MyAuthenticationProvider();
        // 设置userDetailsService
        provider.setUserDetailsService(baseUserDetailService);
        // 禁止隐藏用户未找到异常
        provider.setHideUserNotFoundExceptions(false);
        // 使用BCrypt进行密码的hash
//        provider.setPasswordEncoder(passwordEncoder());
        return provider;
    }
}

过滤器:

/**
 * Description: No Description
 *
 * @author ZhuZiKai
 * @date 2020/2/14 0014
 */
public class MyLoginAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    @Autowired
    private AdminMapper adminMapper;
    /**
     * 登陆类型
     */
    private static final String SPRING_SECURITY_RESTFUL_TYPE_KEY = "type";

    public static final String SPRING_SECURITY_RESTFUL_TYPE_WECHAT = "weChat";
    public static final String SPRING_SECURITY_RESTFUL_TYPE_PHONE = "phone";
    public static final String SPRING_SECURITY_RESTFUL_TYPE_DEFAULT = "user";

    /**
     * 登陆终端:1:移动端0:PC后台登陆
     */
    private static final String SPRING_SECURITY_RESTFUL_ENCRYPTEDDATA_KEY = "encryptedData";
    private static final String SPRING_SECURITY_RESTFUL_IV_KEY = "iv";
    private static final String SPRING_SECURITY_RESTFUL_CODE_KEY = "js_code";

    /**
     * 手机 验证码
     */
    private static final String SPRING_SECURITY_RESTFUL_MOBILE_KEY = "mobile";
    private static final String SPRING_SECURITY_RESTFUL_IDENTIFYCODE_KEY = "identifyCode";

    private static final String SPRING_SECURITY_RESTFUL_USERNAME_KEY = "username";
    private static final String SPRING_SECURITY_RESTFUL_PASSWORD_KEY = "password";
//    private static final String SPRING_SECURITY_RESTFUL_WeChat_KEY = "openId";

    private static final String SPRING_SECURITY_RESTFUL_LOGIN_URL = "/login";
    private boolean postOnly = true;

    public MyLoginAuthenticationFilter() {
        super(new AntPathRequestMatcher(SPRING_SECURITY_RESTFUL_LOGIN_URL, "GET"));
    }

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

        String type = obtainParameter(request, SPRING_SECURITY_RESTFUL_TYPE_KEY);
        String mobile = obtainParameter(request, SPRING_SECURITY_RESTFUL_MOBILE_KEY);
        MyAuthenticationToken authRequest;
        String principal;
        String credentials;

        //微信公众号登录
        if (SPRING_SECURITY_RESTFUL_TYPE_WECHAT.equals(type)) {
            String encryptedData = obtainParameter(request, SPRING_SECURITY_RESTFUL_ENCRYPTEDDATA_KEY).replace(" ", "+");
            String iv = obtainParameter(request, SPRING_SECURITY_RESTFUL_IV_KEY);
            String code = obtainParameter(request, SPRING_SECURITY_RESTFUL_CODE_KEY);

            //就是破解后的openid
            principal = WxTools.doLogin(encryptedData, iv, code);
//            int i = adminMapper.updateOpenid(encryptedData,principal);
//            System.out.println(principal);
            credentials = null;
        }
//      手机验证码登录
        else if (SPRING_SECURITY_RESTFUL_TYPE_PHONE.equals(type)) {
            principal = obtainParameter(request, SPRING_SECURITY_RESTFUL_MOBILE_KEY);
            credentials = obtainParameter(request, SPRING_SECURITY_RESTFUL_IDENTIFYCODE_KEY);
        }
        // 账号密码登陆
        else {
            principal = obtainParameter(request, SPRING_SECURITY_RESTFUL_USERNAME_KEY);
            credentials = DigestUtil.md5Hex(principal + obtainParameter(request, SPRING_SECURITY_RESTFUL_PASSWORD_KEY));
            if (type == null) {
                type = SPRING_SECURITY_RESTFUL_TYPE_DEFAULT;
            }
        }

        if (principal == null) {
            principal = "";
        }
        if (credentials == null) {
            credentials = "";
        }
        principal = principal.trim();
//        System.out.println(principal);
//        System.out.println(credentials);
//        String userName = obtainParameter(request, SPRING_SECURITY_RESTFUL_USERNAME_KEY);
//        adminMapper.updateOpenid(obtainParameter(request, SPRING_SECURITY_RESTFUL_ENCRYPTEDDATA_KEY).replace(" ", "+"),principal);
        authRequest = new MyAuthenticationToken(
                principal, credentials, type, mobile);
        // Allow subclasses to set the "details" property
        setDetails(request, authRequest);
        return this.getAuthenticationManager().authenticate(authRequest);
    }

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

    private String obtainParameter(HttpServletRequest request, String parameter) {
        return request.getParameter(parameter);
    }
}

自定义authentication

/**
 * Description:   自定义 AbstractAuthenticationToken,
 * 新增属性 type: 登陆类型、mobile:移动端设备id
 *
 * @author ZhuZiKai
 * @date 2020/2/14 0014
 */
public class MyAuthenticationToken extends AbstractAuthenticationToken {

    private static final long serialVersionUID = 110L;
    private final Object principal;
    private Object credentials;
    private String type;
    private String mobile;

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

    public MyAuthenticationToken(Object principal, Object credentials, String type, String mobile, Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        this.credentials = credentials;
        this.type = type;
        this.mobile = mobile;
        super.setAuthenticated(true);
    }

    @Override
    public Object getCredentials() {
        return this.credentials;
    }

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

    public String getType() {
        return this.type;
    }

    public String getMobile() {
        return this.mobile;
    }

    @Override
    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");
        } else {
            super.setAuthenticated(false);
        }
    }

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

自定义的MyAbstractUserDetailsAuthenticationProvider 抽象类:

/**
 * Description: No Description
 *
 * @author ZhuZiKai
 * @date 2020/2/14 0014
 */
public abstract class MyAbstractUserDetailsAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware {

    protected final Log logger = LogFactory.getLog(this.getClass());
    protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
    private UserCache userCache = new NullUserCache();
    private boolean forcePrincipalAsString = false;
    protected boolean hideUserNotFoundExceptions = true;
    private UserDetailsChecker preAuthenticationChecks = new MyAbstractUserDetailsAuthenticationProvider.DefaultPreAuthenticationChecks();
    private UserDetailsChecker postAuthenticationChecks = new MyAbstractUserDetailsAuthenticationProvider.DefaultPostAuthenticationChecks();
    private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();

    public MyAbstractUserDetailsAuthenticationProvider() {
    }

    /**
     * 自定义验证
     *
     * @param var1
     * @param var2
     * @throws AuthenticationException
     */
    protected abstract void additionalAuthenticationChecks(UserDetails var1, MyAuthenticationToken var2) throws AuthenticationException;

    @Override
    public final void afterPropertiesSet() throws Exception {
        Assert.notNull(this.userCache, "A user cache must be set");
        Assert.notNull(this.messages, "A message source must be set");
        this.doAfterPropertiesSet();
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        // 此处修改断言自定义的 MyAuthenticationToken
        Assert.isInstanceOf(MyAuthenticationToken.class, authentication, this.messages.getMessage("MyAbstractUserDetailsAuthenticationProvider.onlySupports", "Only MyAuthenticationToken is supported"));
        String username = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName();
        boolean cacheWasUsed = true;
        UserDetails user = this.userCache.getUserFromCache(username);
        if (user == null) {
            cacheWasUsed = false;

            try {
                user = this.retrieveUser(username, (MyAuthenticationToken) authentication);
            } catch (UsernameNotFoundException var6) {
                this.logger.debug("User \'" + username + "\' not found");
                if (this.hideUserNotFoundExceptions) {
                    throw new BadCredentialsException(this.messages.getMessage("MyAbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
                }

                throw var6;
            }

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

        try {
            this.preAuthenticationChecks.check(user);
            this.additionalAuthenticationChecks(user, (MyAuthenticationToken) authentication);
        } catch (AuthenticationException var7) {
            if (!cacheWasUsed) {
                throw var7;
            }

            cacheWasUsed = false;
            user = this.retrieveUser(username, (MyAuthenticationToken) authentication);
            this.preAuthenticationChecks.check(user);
            this.additionalAuthenticationChecks(user, (MyAuthenticationToken) authentication);
        }

        this.postAuthenticationChecks.check(user);
        if (!cacheWasUsed) {
            this.userCache.putUserInCache(user);
        }

        Object principalToReturn = user;
        if (this.forcePrincipalAsString) {
            principalToReturn = user.getUsername();
        }

        return this.createSuccessAuthentication(principalToReturn, authentication, user);
    }

    protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
        MyAuthenticationToken result = new MyAuthenticationToken(principal, authentication.getCredentials(), ((MyAuthenticationToken) authentication).getType(), ((MyAuthenticationToken) authentication).getMobile(), this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
        result.setDetails(authentication.getDetails());
        return result;
    }

    protected void doAfterPropertiesSet() throws Exception {
    }


    public UserCache getUserCache() {
        return this.userCache;
    }

    public boolean isForcePrincipalAsString() {
        return this.forcePrincipalAsString;
    }

    public boolean isHideUserNotFoundExceptions() {
        return this.hideUserNotFoundExceptions;
    }

    /**
     * 检索用户
     *
     * @param var1
     * @param var2
     * @return
     * @throws AuthenticationException
     */
    protected abstract UserDetails retrieveUser(String var1, MyAuthenticationToken var2) throws AuthenticationException;

    public void setForcePrincipalAsString(boolean forcePrincipalAsString) {
        this.forcePrincipalAsString = forcePrincipalAsString;
    }

    public void setHideUserNotFoundExceptions(boolean hideUserNotFoundExceptions) {
        this.hideUserNotFoundExceptions = hideUserNotFoundExceptions;
    }

    @Override
    public void setMessageSource(MessageSource messageSource) {
        this.messages = new MessageSourceAccessor(messageSource);
    }

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

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

    protected UserDetailsChecker getPreAuthenticationChecks() {
        return this.preAuthenticationChecks;
    }

    public void setPreAuthenticationChecks(UserDetailsChecker preAuthenticationChecks) {
        this.preAuthenticationChecks = preAuthenticationChecks;
    }

    protected UserDetailsChecker getPostAuthenticationChecks() {
        return this.postAuthenticationChecks;
    }

    public void setPostAuthenticationChecks(UserDetailsChecker postAuthenticationChecks) {
        this.postAuthenticationChecks = postAuthenticationChecks;
    }

    public void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) {
        this.authoritiesMapper = authoritiesMapper;
    }

    private class DefaultPostAuthenticationChecks implements UserDetailsChecker {
        private DefaultPostAuthenticationChecks() {
        }

        @Override
        public void check(UserDetails user) {
            if (!user.isCredentialsNonExpired()) {
                MyAbstractUserDetailsAuthenticationProvider.this.logger.debug("User account credentials have expired");
                throw new CredentialsExpiredException(MyAbstractUserDetailsAuthenticationProvider.this.messages.getMessage("MyAbstractUserDetailsAuthenticationProvider.credentialsExpired", "User credentials have expired"));
            }
        }
    }

    private class DefaultPreAuthenticationChecks implements UserDetailsChecker {
        private DefaultPreAuthenticationChecks() {
        }

        @Override
        public void check(UserDetails user) {
            if (!user.isAccountNonLocked()) {
                MyAbstractUserDetailsAuthenticationProvider.this.logger.debug("User account is locked");
                throw new LockedException(MyAbstractUserDetailsAuthenticationProvider.this.messages.getMessage("MyAbstractUserDetailsAuthenticationProvider.locked", "User account is locked"));
            } else if (!user.isEnabled()) {
                MyAbstractUserDetailsAuthenticationProvider.this.logger.debug("User account is disabled");
                throw new DisabledException(MyAbstractUserDetailsAuthenticationProvider.this.messages.getMessage("MyAbstractUserDetailsAuthenticationProvider.disabled", "User is disabled"));
            } else if (!user.isAccountNonExpired()) {
                MyAbstractUserDetailsAuthenticationProvider.this.logger.debug("User account is expired");
                throw new AccountExpiredException(MyAbstractUserDetailsAuthenticationProvider.this.messages.getMessage("MyAbstractUserDetailsAuthenticationProvider.expired", "User account has expired"));
            }
        }
    }
}

provide:

/**
 * Description: 自定义密码验证
 * 实现自定义的MyAbstractUserDetailsAuthenticationProvider 抽象类
 * 根据登陆的类型 执行不同的校验
 *
 * @author ZhuZiKai
 * @date 2020/2/14 0014
 */
@Component
public class MyAuthenticationProvider extends MyAbstractUserDetailsAuthenticationProvider {

    private PasswordEncoder passwordEncoder;
    private String userNotFoundEncodedPassword;
    private SaltSource saltSource;
    private UserDetailsService userDetailsService;

    @Resource
    private SmsService smsService;
    private static MyAuthenticationProvider myAuthenticationProvider;

    @PostConstruct
    public void init() {
        myAuthenticationProvider = this;
        myAuthenticationProvider.smsService = this.smsService;
    }


    public MyAuthenticationProvider() {
        this.setPasswordEncoder((PasswordEncoder) (new PlaintextPasswordEncoder()));
    }

    /**
     * 自定义验证
     *
     * @param userDetails
     * @param authentication
     * @throws AuthenticationException
     */
    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails, MyAuthenticationToken authentication) throws AuthenticationException {
        Object salt = null;
        if (this.saltSource != null) {
            salt = this.saltSource.getSalt(userDetails);
        }

        if (authentication.getCredentials() == null) {
            this.logger.debug("Authentication failed: no credentials provided");
            throw new BadCredentialsException(this.messages.getMessage("MyAbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        } else {
            String presentedUsername = authentication.getPrincipal().toString();
            String presentedPassword = authentication.getCredentials().toString();

            // 验证开始
            if (MyLoginAuthenticationFilter.SPRING_SECURITY_RESTFUL_TYPE_WECHAT.equals(authentication.getType())) {
                // 微信只需要根据 SPRING_SECURITY_RESTFUL_TYPE_WECHAT 查询到微信号即可,所以此处无需验证
            } else if (MyLoginAuthenticationFilter.SPRING_SECURITY_RESTFUL_TYPE_PHONE.equals(authentication.getType())) {
                // 手机短信登录
                // 验证码验证,调用公共服务查询 key 为authentication.getPrincipal()的value, 并判断其与验证码是否匹配
                int res = smsService.checkIsCorrectCode(presentedUsername, presentedPassword).getStatus();
                if (res != 1) {
                    this.logger.debug("Authentication failed: verifyCode does not match stored value");
                    throw new BadCredentialsException(this.messages.getMessage("MyAbstractUserDetailsAuthenticationProvider.badCredentials", "Bad verifyCode"));
                }
            } else {
                // 用户名密码验证
                if (!this.passwordEncoder.isPasswordValid(userDetails.getPassword(), presentedPassword, salt)) {
                    this.logger.debug("Authentication failed: password does not match stored value");
                    throw new BadCredentialsException(this.messages.getMessage("MyAbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
                }
            }
        }
    }

    @Override
    protected void doAfterPropertiesSet() throws Exception {
        Assert.notNull(this.userDetailsService, "A UserDetailsService must be set");
    }

    @Override
    protected final UserDetails retrieveUser(String username, MyAuthenticationToken authentication) throws AuthenticationException {
        UserDetails loadedUser;
        try {
            // 调用loadUserByUsername时加入type前缀
            loadedUser = this.getUserDetailsService().loadUserByUsername(authentication.getType() + "&:@" + username);
        } catch (UsernameNotFoundException var6) {
            if (authentication.getCredentials() != null) {
                String presentedPassword = authentication.getCredentials().toString();
                this.passwordEncoder.isPasswordValid(this.userNotFoundEncodedPassword, presentedPassword, null);
            }
            throw var6;
        } catch (Exception var7) {
            throw new InternalAuthenticationServiceException(var7.getMessage(), var7);
        }

        if (loadedUser == null) {
            throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
        } else {
            return loadedUser;
        }
    }

    public void setPasswordEncoder(Object passwordEncoder) {
        Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
        if (passwordEncoder instanceof PasswordEncoder) {
            this.setPasswordEncoder((PasswordEncoder) passwordEncoder);
        } else if (passwordEncoder instanceof org.springframework.security.crypto.password.PasswordEncoder) {
            final org.springframework.security.crypto.password.PasswordEncoder delegate = (org.springframework.security.crypto.password.PasswordEncoder) passwordEncoder;
            this.setPasswordEncoder(new PasswordEncoder() {
                @Override
                public String encodePassword(String rawPass, Object salt) {
                    this.checkSalt(salt);
                    return delegate.encode(rawPass);
                }

                @Override
                public boolean isPasswordValid(String encPass, String rawPass, Object salt) {
                    this.checkSalt(salt);
                    return delegate.matches(rawPass, encPass);
                }

                private void checkSalt(Object salt) {
                    Assert.isNull(salt, "Salt value must be null when used with crypto module PasswordEncoder");
                }
            });
        } else {
            throw new IllegalArgumentException("passwordEncoder must be a PasswordEncoder instance");
        }
    }

    private void setPasswordEncoder(PasswordEncoder passwordEncoder) {
        Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
        this.userNotFoundEncodedPassword = passwordEncoder.encodePassword("userNotFoundPassword", (Object) null);
        this.passwordEncoder = passwordEncoder;
    }

    protected PasswordEncoder getPasswordEncoder() {
        return this.passwordEncoder;
    }

    public void setSaltSource(SaltSource saltSource) {
        this.saltSource = saltSource;
    }

    protected SaltSource getSaltSource() {
        return this.saltSource;
    }

    public void setUserDetailsService(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    protected UserDetailsService getUserDetailsService() {
        return this.userDetailsService;
    }
}
这个方法其实最主要的也就这一句:

loadedUser = this.getUserDetailsService().loadUserByUsername(username);,我们继续往里面看,会发现loadUserByUsername其实是UserDetailsService接口下的一个方法,到这里我们就明白了,如果之后我们要自己查询数据,那么我们就必须要实现这个接口,然后重新写这个loadUserByUsername方法并返回UserDetails。
所以这里我们重写一下loadUserByUsername方法,实现UserDetailsService类

BaseUserDetailServiceImpl:
/**
 * Description: No Description
 *
 * @author ZhuZiKai
 * @date 2020/2/14 0014
 */
@Service
@Transactional(isolation = Isolation.DEFAULT, propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public class BaseUserDetailServiceImpl implements UserDetailsService {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private AdminService adminService;
    @Autowired
    private RoleService roleService;
    @Autowired
    private PermissionService permissionService;

    @Override
    public UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException {

        Admin admin;
        String[] parameter;
        int index = var1.indexOf("&:@");
        if (index != -1) {
            parameter = var1.split("&:@");
        } else {
            // 如果是 refresh_token 不分割
            parameter = new String[]{MyLoginAuthenticationFilter.SPRING_SECURITY_RESTFUL_TYPE_DEFAULT, var1};
        }

        if (MyLoginAuthenticationFilter.SPRING_SECURITY_RESTFUL_TYPE_WECHAT.equals(parameter[0])) {
            // weixin登陆根据openId查询用户
            Admin adminData = adminService.findAdminByOpenid(parameter[1]);

            if (adminData == null) {
                logger.error("找不到该用户,微信号:" + parameter[1]);
                System.out.println("找不到该用户,微信号:" + parameter[1]);
                throw new UsernameNotFoundException(parameter[1]);
            }
            admin = adminData;
        } else if (MyLoginAuthenticationFilter.SPRING_SECURITY_RESTFUL_TYPE_PHONE.equals(parameter[0])) {
            // 验证码登录
            Admin adminData = adminService.findAdminByPhone(parameter[1]);
            if (adminData == null) {
                logger.error("找不到该用户,手机号码:" + parameter[1]);
                throw new UsernameNotFoundException("找不到该用户,手机号码:" + parameter[1]);
            }
            admin = adminData;
        } else {
            // 账号密码登陆根据用户名查询用户
            Admin adminData = adminService.findAdminByUserName(parameter[1]);
            if (adminData == null) {
                logger.error("找不到该用户,用户名:" + parameter[1]);
                throw new UsernameNotFoundException("找不到该用户,用户名:" + parameter[1]);
            }
            admin = adminData;
        }

        Set<GrantedAuthority> grantedAuthorities = new HashSet<>();
        List<Role> roleResult = roleService.findRoleByAdminId(admin.getId());
        if (roleResult != null) {
            for (Role role : roleResult) {
                //角色必须是ROLE_开头,可以在数据库中设置
                GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_" + role.getValue());
                grantedAuthorities.add(grantedAuthority);
                //获取权限
                List<Permission> perResult = permissionService.getPermissionByRoleId(role.getId());
                if (perResult != null) {

                    for (Permission permission : perResult) {
                        GrantedAuthority authority = new SimpleGrantedAuthority(permission.getUrl());
                        grantedAuthorities.add(authority);
                    }
                }
            }
        }

        // 返回带有用户权限信息的User
        User user = new User(admin.getUsername(), admin.getPassword(), isActive(admin.getStatus()), true, true, true, grantedAuthorities);
        return user;
    }

    //判断账号是否禁用
    private boolean isActive(int status) {
        return status == 1 ? true : false;
    }
}

生成的Token选择存入redis或者存入数据库,方便后面访问接口进行校验

/**
 * Description: No Description
 *
 * @author ZhuZiKai
 * @date 2020/2/14 0014
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private DataSource dataSource;

    /**
     * 后加的
     */
    @Autowired
    private BaseUserDetailServiceImpl userDetailsService;

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    /**
     * token存储redis 二选一
     */
    @Bean
    RedisTokenStore redisTokenStore() {
        return new RedisTokenStore(redisConnectionFactory);
    }

    /**
     * token存储数据库 二选一
     *
     * @return
     */
//    @Bean
//    public JdbcTokenStore jdbcTokenStore() {
//        return new JdbcTokenStore(dataSource);
//    }

    @Bean
    public ClientDetailsService clientDetails() {
        return new JdbcClientDetailsService(dataSource);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(clientDetails());
    }

    @Bean
    public WebResponseExceptionTranslator<OAuth2Exception> webResponseExceptionTranslator() {
        return new MssWebResponseExceptionTranslator();
    }


    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .tokenStore(redisTokenStore())
//                二选一
//                .tokenStore(jdbcTokenStore())
                .userDetailsService(userDetailsService)
                .authenticationManager(authenticationManager);
        endpoints.tokenServices(defaultTokenServices());
        //认证异常翻译
        endpoints.exceptionTranslator(webResponseExceptionTranslator());
    }

    @Primary
    @Bean
    public DefaultTokenServices defaultTokenServices() {
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(redisTokenStore());
//        二选一
//        tokenServices.setTokenStore(jdbcTokenStore());
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setClientDetailsService(clientDetails());
        //access_token 有效期自定义设置,默认12小时
        tokenServices.setAccessTokenValiditySeconds(60 * 60 * 12);
        //refresh_token 默认30天,这里修改
        tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);
        return tokenServices;
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.tokenKeyAccess("permitAll()");
        security.checkTokenAccess("isAuthenticated()");
        security.allowFormAuthenticationForClients();
    }
}

最后就是创建两个登录成功或者登陆失败的响应类了CustomAuthenticationSuccessHandler和CustomAuthenticationFailureHandler
CustomAuthenticationSuccessHandler:

/**
 * Description: No Description
 *
 * @author ZhuZiKai
 * @date 2020/2/14 0014
 */
@Component("customAuthenticationSuccessHandler")
public class CustomAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
    private static final Logger logger = LoggerFactory.getLogger(CustomAuthenticationSuccessHandler.class.getName());

    @Autowired
    private AdminService adminService;

    @Autowired
    private AdminMapper adminMapper;

    @Autowired
    private AreaService areaService;

    @Autowired
    private LogService logService;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private ClientDetailsService clientDetailsService;

    @Autowired
    private AuthorizationServerTokenServices authorizationServerTokenServices;

    @Autowired
    private ConsumerTokenServices consumerTokenServices;

    public CustomAuthenticationSuccessHandler() {
        logger.info("CustomAuthenticationSuccessHandler loading ...");
    }

    /**
     * 登录成功被调用
     */
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
        // authentication:封装认证信息(用户信息等)

        String loginIp = getIpAddress(request);
        Admin currentUser = adminService.findAdminByUserName(authentication.getName());

        String header = request.getHeader("Authorization");
        String loginType = request.getParameter("type");

        String openid = request.getParameter("openid");
        String username = request.getParameter("username");

        if (!StringUtils.isEmpty(openid)) {
            adminMapper.updateOpenid(username, openid);
        }

        //没有client信息
        if (header == null || !header.startsWith("Basic ")) {
            throw new UnapprovedClientAuthenticationException("请求头中无client信息");
        }
        //base64解码获取clientId、clientSecret
        String[] tokens = extractAndDecodeHeader(header, request);
        assert tokens.length == 2;

        String clientId = tokens[0];
        String clientSecret = tokens[1];

        ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);

        if (clientDetails == null) {
            throw new UnapprovedClientAuthenticationException("clientId对应的配置信息不存在,clientId:" + clientId);
        } else if (!StringUtils.equals(clientDetails.getClientSecret(), clientSecret)) {
            throw new UnapprovedClientAuthenticationException("clientSecret不匹配,clientId:" + clientId);
        }
//创建accessToken
        JSONObject jsonObject = generateToken(authentication, clientId, clientDetails);

//        //判断异地登陆
//        if (!"0:0:0:0:0:0:0:1".equals(loginIp)) {
//            boolean offsiteLanding = isOffsiteLanding(currentUser.getUsername(), loginIp);
//            //是异地登陆
//            if (offsiteLanding) {
//                consumerTokenServices.revokeToken(jsonObject.get("access_token").toString());
//                jsonObject = generateToken(authentication, clientId, clientDetails);
//            }
//        }

        //创建accessToken
//        TokenRequest tokenRequest = new TokenRequest(MapUtils.EMPTY_MAP, clientId, clientDetails.getScope(), "custom");
//
//        OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
//
//        OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);
//
//        OAuth2AccessToken accessToken = authorizationServerTokenServices.createAccessToken(oAuth2Authentication);

        response.setContentType("application/json;charset=UTF-8");

        SimpleDateFormat sdf = new SimpleDateFormat("EEE MMM dd yyyy HH:mm:ss 'GMT+0800' (中国标准时间)", Locale.ENGLISH);

        jsonObject.put("adminId", currentUser.getId());
        jsonObject.put("username", currentUser.getUsername());
        jsonObject.put("realname", currentUser.getRealname());
        jsonObject.put("post", currentUser.getPost());
        jsonObject.put("currentTime", sdf.format(DateTools.currentTime()));

        //微信菜单
//        List<Map> menuByUser = adminService.getMenuByUser(currentUser.getId(), 0);
//        for (Map map : menuByUser) {
//            map.put("img", WxMenuImgEnum.getWxMenuImgEnum(map.get("id").toString()));
//        }
//        jsonObject.put("wxmenu", menuByUser);

        //PC端
        if ("user".equals(loginType) || "phone".equals(loginType)) {
            jsonObject.put("menu", adminService.getMenuByUser(currentUser.getId(), 1));
            jsonObject.put("type", adminService.getRoleByUser(currentUser.getId()));
            jsonObject.put("loginCount", logService.addLoginLog(currentUser.getId(), currentUser.getUsername(), loginIp));

            //小程序
        } else {
            jsonObject.put("type", adminService.getRoleByUser(currentUser.getId()));
        }
        PLATFORM_LOGGER.info("用户登录平台:" + currentUser.getRealname());
        //跨域
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.getWriter().write(objectMapper.writeValueAsString(new BaseResp(ResultStatus.SUCCESS, jsonObject)));

    }


    /**
     * base64解码请求头 Basic aW1vb2M6aW1vb2NzZWNyZXQ=
     * Decodes the header into a username and password.
     *
     * @throws BadCredentialsException if the Basic header is not present or is not valid
     *                                 Base64
     */
    private String[] extractAndDecodeHeader(String header, HttpServletRequest request)
            throws IOException {
        //Basic aW1vb2M6aW1vb2NzZWNyZXQ= 截取Basic后的
        byte[] base64Token = header.substring(6).getBytes("UTF-8");
        byte[] decoded;
        try {
            //解码后格式   用户名:密码
            decoded = Base64.decode(base64Token);
        } catch (IllegalArgumentException e) {
            throw new BadCredentialsException(
                    "Failed to decode basic authentication token");
        }

        String token = new String(decoded, "UTF-8");

        int delim = token.indexOf(":");

        if (delim == -1) {
            throw new BadCredentialsException("Invalid basic authentication token");
        }
        //返回的数组是   [用户名(就是client_id),clientSecret] 其实就是配置的
        /**
         * security.oauth2.client.clientId = imooc
         security.oauth2.client.clientSecret = imoocsecret
         */
        return new String[]{token.substring(0, delim), token.substring(delim + 1)};
    }

    /**
     * 是否异地登陆
     * 传入是手机就不进来 所以传进来的IP一定是电脑登陆的IP
     *
     * @param username
     * @param ip
     * @return true是异地登陆  flase同一地点登陆
     */
    private boolean isOffsiteLanding(String username, String ip) {
        List<Log> lastLogins = logService.getLastLogin(username);
        if (lastLogins == null || lastLogins.size() == 0) {
            return false;
        }
        //如果一样 代表手机登陆 不挤掉,在往上一次查找
        if ("0:0:0:0:0:0:0:1".equals(lastLogins.get(0).getIpAddress())) {
            if (lastLogins.size() == 1) {
                return false;
            }

            //同一地点登陆
            if (ip.equals(lastLogins.get(1).getIpAddress())) {
                return false;
            }
            return true;
        }

        if (ip.equals(lastLogins.get(0).getIpAddress())) {
            return false;
        }

        return true;

    }

    /**
     * 生成token
     *
     * @param authentication
     * @return
     * @throws JsonProcessingException
     */
    private JSONObject generateToken(Authentication authentication, String clientId, ClientDetails clientDetails) throws JsonProcessingException {

        TokenRequest tokenRequest = new TokenRequest(MapUtils.EMPTY_MAP, clientId, clientDetails.getScope(), "custom");

        OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);

        OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);

        OAuth2AccessToken accessToken = authorizationServerTokenServices.createAccessToken(oAuth2Authentication);

        String accessTokenStr = objectMapper.writeValueAsString(accessToken);
        JSONObject jsonObject = JSONObject.fromObject(accessTokenStr);

        return jsonObject;
    }

CustomAuthenticationFailureHandler:

/**
 * Description: No Description
 *
 * @author ZhuZiKai
 * @date 2020/2/14 0014
 */
@Component("customAuthenticationFailureHandler")
public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
    private static final Logger logger = LoggerFactory.getLogger(CustomAuthenticationFailureHandler.class.getName());

    @Autowired
    private ObjectMapper objectMapper;

    public CustomAuthenticationFailureHandler() {
        logger.info("CustomAuthenticationFailureHandler loading ...");
    }

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {
        BaseResp result = new BaseResp();

        if (exception instanceof BadCredentialsException) {
            result.setStatus(ResultStatus.pwd_not_exist.getErrorStatus());
            result.setMessage(ResultStatus.pwd_not_exist.getErrorMsg());
        } else if (exception instanceof UsernameNotFoundException) {
            //用户不存在
            result.setStatus(ResultStatus.user_not_exist.getErrorStatus());
            result.setData(exception.getMessage());
            result.setMessage(ResultStatus.user_not_exist.getErrorMsg());
        } else {
            result.setStatus(ResultStatus.FAIL.getErrorStatus());
            result.setMessage("登陆失败!");
        }

        //exception:认证过程中的错误
        logger.error("Authentication does not pass" + ":密码错误");

        int status = HttpStatus.OK.value();
        response.setStatus(status);
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
        response.getWriter().write(objectMapper.writeValueAsString(result));
    }
}

总结

代码复制即用,有什么问题欢迎留言,哪里写的不对的话也可以直接怼我


这是一位对spring security有特别研究得一位大佬:
https://felord.cn/categories/spring-security/

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_Romeo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值