diff --git a/fast-framework/src/main/java/net/maku/framework/security/config/SecurityConfig.java b/fast-framework/src/main/java/net/maku/framework/security/config/SecurityConfig.java index 5b05d19..e36b6c1 100644 --- a/fast-framework/src/main/java/net/maku/framework/security/config/SecurityConfig.java +++ b/fast-framework/src/main/java/net/maku/framework/security/config/SecurityConfig.java @@ -2,43 +2,74 @@ package net.maku.framework.security.config; import lombok.AllArgsConstructor; import net.maku.framework.security.exception.SecurityAuthenticationEntryPoint; +import net.maku.framework.security.mobile.MobileAuthenticationProvider; +import net.maku.framework.security.mobile.MobileUserDetailsService; +import net.maku.framework.security.mobile.MobileVerifyCodeService; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; -import org.springframework.security.authentication.AuthenticationEventPublisher; import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.DefaultAuthenticationEventPublisher; -import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.authentication.ProviderManager; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.filter.OncePerRequestFilter; +import java.util.ArrayList; import java.util.List; +/** + * SpringSecurity 配置文件 + * + * @author 阿沐 babamu@126.com + */ @Configuration @AllArgsConstructor @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig { - private final UserDetailsService userDetailsService; private final OncePerRequestFilter authenticationTokenFilter; private final PermitResource permitResource; + private final UserDetailsService userDetailsService; + private final MobileUserDetailsService mobileUserDetailsService; + private final MobileVerifyCodeService mobileVerifyCodeService; + private final PasswordEncoder passwordEncoder; + private final ApplicationEventPublisher applicationEventPublisher; @Bean - public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { - return authenticationConfiguration.getAuthenticationManager(); + DaoAuthenticationProvider daoAuthenticationProvider() { + DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(); + daoAuthenticationProvider.setPasswordEncoder(passwordEncoder); + daoAuthenticationProvider.setUserDetailsService(userDetailsService); + + return daoAuthenticationProvider; } @Bean - public AuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { - return new DefaultAuthenticationEventPublisher(applicationEventPublisher); + MobileAuthenticationProvider mobileAuthenticationProvider() { + return new MobileAuthenticationProvider(mobileUserDetailsService, mobileVerifyCodeService); + } + + @Bean + public AuthenticationManager authenticationManager() { + List providerList = new ArrayList<>(); + providerList.add(daoAuthenticationProvider()); + providerList.add(mobileAuthenticationProvider()); + + ProviderManager providerManager = new ProviderManager(providerList); + providerManager.setAuthenticationEventPublisher(new DefaultAuthenticationEventPublisher(applicationEventPublisher)); + + return providerManager; } @Bean @@ -53,8 +84,7 @@ public class SecurityConfig { .and().authorizeRequests() .antMatchers(permits).permitAll() .anyRequest().authenticated() - .and().userDetailsService(userDetailsService) - .exceptionHandling().authenticationEntryPoint(new SecurityAuthenticationEntryPoint()) + .and().exceptionHandling().authenticationEntryPoint(new SecurityAuthenticationEntryPoint()) .and().headers().frameOptions().disable() .and().csrf().disable() ; diff --git a/fast-framework/src/main/java/net/maku/framework/security/mobile/MobileAuthenticationProvider.java b/fast-framework/src/main/java/net/maku/framework/security/mobile/MobileAuthenticationProvider.java new file mode 100644 index 0000000..191743e --- /dev/null +++ b/fast-framework/src/main/java/net/maku/framework/security/mobile/MobileAuthenticationProvider.java @@ -0,0 +1,87 @@ +package net.maku.framework.security.mobile; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.MessageSource; +import org.springframework.context.MessageSourceAware; +import org.springframework.context.support.MessageSourceAccessor; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.SpringSecurityMessageSource; +import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; +import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.util.Assert; + +/** + * 手机短信登录 AuthenticationProvider + * + * @author 阿沐 babamu@126.com + */ +public class MobileAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware { + protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); + private final GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper(); + private final MobileUserDetailsService mobileUserDetailsService; + private final MobileVerifyCodeService mobileVerifyCodeService; + + public MobileAuthenticationProvider(MobileUserDetailsService mobileUserDetailsService, MobileVerifyCodeService mobileVerifyCodeService) { + this.mobileUserDetailsService = mobileUserDetailsService; + this.mobileVerifyCodeService = mobileVerifyCodeService; + } + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + Assert.isInstanceOf(MobileAuthenticationToken.class, authentication, + () -> messages.getMessage( + "MobileAuthenticationProvider.onlySupports", + "Only MobileAuthenticationProvider is supported")); + + MobileAuthenticationToken authenticationToken = (MobileAuthenticationToken) authentication; + String mobile = authenticationToken.getName(); + String code = (String) authenticationToken.getCredentials(); + + try { + UserDetails userDetails = mobileUserDetailsService.loadUserByMobile(mobile); + if (userDetails == null) { + throw new BadCredentialsException("Bad credentials"); + } + + // 短信验证码效验 + if (mobileVerifyCodeService.verifyCode(mobile, code)) { + return createSuccessAuthentication(authentication, userDetails); + } else { + throw new BadCredentialsException("mobile code is not matched"); + } + } catch (UsernameNotFoundException ex) { + throw new BadCredentialsException(this.messages + .getMessage("MobileAuthenticationProvider.badCredentials", "Bad credentials")); + } + + } + + protected Authentication createSuccessAuthentication(Authentication authentication, UserDetails user) { + MobileAuthenticationToken result = new MobileAuthenticationToken(user, null, + authoritiesMapper.mapAuthorities(user.getAuthorities())); + result.setDetails(authentication.getDetails()); + return result; + } + + @Override + public boolean supports(Class authentication) { + return MobileAuthenticationToken.class.isAssignableFrom(authentication); + } + + @Override + public void afterPropertiesSet() throws Exception { + Assert.notNull(mobileUserDetailsService, "mobileUserDetailsService must not be null"); + Assert.notNull(mobileVerifyCodeService, "mobileVerifyCodeService must not be null"); + } + + @Override + public void setMessageSource(MessageSource messageSource) { + this.messages = new MessageSourceAccessor(messageSource); + } + +} diff --git a/fast-framework/src/main/java/net/maku/framework/security/mobile/MobileAuthenticationToken.java b/fast-framework/src/main/java/net/maku/framework/security/mobile/MobileAuthenticationToken.java new file mode 100644 index 0000000..ac246d9 --- /dev/null +++ b/fast-framework/src/main/java/net/maku/framework/security/mobile/MobileAuthenticationToken.java @@ -0,0 +1,56 @@ +package net.maku.framework.security.mobile; + +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.SpringSecurityCoreVersion; +import org.springframework.util.Assert; + +import java.util.Collection; + +/** + * 手机短信登录 AuthenticationToken + * + * @author 阿沐 babamu@126.com + */ +public class MobileAuthenticationToken extends AbstractAuthenticationToken { + private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; + private final Object principal; + private String code; + + public MobileAuthenticationToken(Object principal, String code) { + super(null); + this.principal = principal; + this.code = code; + setAuthenticated(false); + } + + public MobileAuthenticationToken(Object principal, String code, Collection authorities) { + super(authorities); + this.principal = principal; + this.code = code; + super.setAuthenticated(true); + } + + @Override + public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { + Assert.isTrue(!isAuthenticated, + "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead"); + super.setAuthenticated(false); + } + + @Override + public Object getCredentials() { + return this.code; + } + + @Override + public Object getPrincipal() { + return this.principal; + } + + @Override + public void eraseCredentials() { + super.eraseCredentials(); + this.code = null; + } +} diff --git a/fast-framework/src/main/java/net/maku/framework/security/mobile/MobileUserDetailsService.java b/fast-framework/src/main/java/net/maku/framework/security/mobile/MobileUserDetailsService.java new file mode 100644 index 0000000..c4c95ea --- /dev/null +++ b/fast-framework/src/main/java/net/maku/framework/security/mobile/MobileUserDetailsService.java @@ -0,0 +1,21 @@ +package net.maku.framework.security.mobile; + +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +/** + * 手机短信登录,UserDetailsService + * + * @author 阿沐 babamu@126.com + */ +public interface MobileUserDetailsService { + + /** + * 通过手机号加载用户信息 + * + * @param mobile 手机号 + * @return 用户信息 + * @throws UsernameNotFoundException 不存在异常 + */ + UserDetails loadUserByMobile(String mobile) throws UsernameNotFoundException; +} diff --git a/fast-framework/src/main/java/net/maku/framework/security/mobile/MobileVerifyCodeService.java b/fast-framework/src/main/java/net/maku/framework/security/mobile/MobileVerifyCodeService.java new file mode 100644 index 0000000..757aee6 --- /dev/null +++ b/fast-framework/src/main/java/net/maku/framework/security/mobile/MobileVerifyCodeService.java @@ -0,0 +1,11 @@ +package net.maku.framework.security.mobile; + +/** + * 手机短信登录,验证码效验 + * + * @author 阿沐 babamu@126.com + */ +public interface MobileVerifyCodeService { + + boolean verifyCode(String mobile, String code); +}