實現/oauth/token路由下能夠適配全部的登陸類型,自定義參數mysql
基於Spring Boot建立項目server-auth
https://start.spring.io/
在pom.xml添加lombok,而且idea安裝了lombok插件(不會安裝,百度一下)git
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>
使用idea打開項目,設置配置文件application.propertiesspring
server.port=8080 spring.datasource.url=jdbc:mysql://localhost:3306/cloud-auth?useUnicode=true&characterEncoding=utf-8&useSSL=false spring.datasource.username=root spring.datasource.password=root spring.datasource.driver-class-name=com.mysql.jdbc.Driver
@Data public class IntegrationAuthenticationEntity { private String authType;//請求登陸認證類型 private Map<String,String[]> authParameters;//請求登陸認證參數集合 public String getAuthParameter(String paramter){ String[] values = this.authParameters.get(paramter); if(values != null && values.length > 0){ return values[0]; } return null; } }
public interface IntegrationAuthenticator { /** * 處理集成認證 * @param entity 集成認證明體 * @return 用戶表實體 */ UserPojo authenticate(IntegrationAuthenticationEntity entity); /** * 預處理 * @param entity 集成認證明體 */ void prepare(IntegrationAuthenticationEntity entity); /** * 判斷是否支持集成認證類型 * @param entity 集成認證明體 */ boolean support(IntegrationAuthenticationEntity entity); /** * 認證結束後執行 * @param entity 集成認證明體 */ void complete(IntegrationAuthenticationEntity entity); }
public abstract class AbstractPreparableIntegrationAuthenticator implements IntegrationAuthenticator { @Override public void prepare(IntegrationAuthenticationEntity entity) { } @Override public void complete(IntegrationAuthenticationEntity entity) { } }
public class IntegrationAuthenticationContext { private static ThreadLocal<IntegrationAuthenticationEntity> holder = new ThreadLocal<>(); public static void set(IntegrationAuthenticationEntity entity){ holder.set(entity); } public static IntegrationAuthenticationEntity get(){ return holder.get(); } public static void clear(){ holder.remove(); } }
@Component public class IntegrationAuthenticationFilter extends GenericFilterBean implements ApplicationContextAware { private static final String AUTH_TYPE_PARM_NAME = "auth_type";//登陸類型參數名 private static final String OAUTH_TOKEN_URL = "/oauth/token";//須要攔截的路由 private RequestMatcher requestMatcher; private ApplicationContext applicationContext; private Collection<IntegrationAuthenticator> authenticators; public IntegrationAuthenticationFilter() { this.requestMatcher = new OrRequestMatcher( new AntPathRequestMatcher(OAUTH_TOKEN_URL, "GET"), new AntPathRequestMatcher(OAUTH_TOKEN_URL, "POST") ); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; if (requestMatcher.matches(request)){ RequestParameterWrapper requestParameterWrapper = new RequestParameterWrapper(request); if (requestParameterWrapper.getParameter("password") == null){ requestParameterWrapper.addParameter("password",""); } IntegrationAuthenticationEntity entity = new IntegrationAuthenticationEntity(); entity.setAuthType(requestParameterWrapper.getParameter(AUTH_TYPE_PARM_NAME)); entity.setAuthParameters(requestParameterWrapper.getParameterMap()); IntegrationAuthenticationContext.set(entity); try { this.prepare(entity); filterChain.doFilter(requestParameterWrapper,servletResponse); this.complete(entity); } finally { IntegrationAuthenticationContext.clear(); } } else { filterChain.doFilter(servletRequest,servletResponse); } } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } /** * 認證前回調 * @param entity 集成認證明體 */ private void prepare(IntegrationAuthenticationEntity entity) { if (entity != null){ synchronized (this){ Map<String, IntegrationAuthenticator> map = applicationContext.getBeansOfType(IntegrationAuthenticator.class); if (map != null){ this.authenticators = map.values(); } } } if (this.authenticators == null){ this.authenticators = new ArrayList<>(); } for (IntegrationAuthenticator authenticator : this.authenticators){ if (authenticator.support(entity)){ authenticator.prepare(entity); } } } /** * 認證結束後回調 * @param entity 集成認證明體 */ private void complete(IntegrationAuthenticationEntity entity) { for (IntegrationAuthenticator authenticator: authenticators) { if(authenticator.support(entity)){ authenticator.complete(entity); } } } /** * 用途:在攔截時給Request添加參數 * Cloud OAuth2 密碼模式須要判斷Request是否存在password參數, * 若是不存在會拋異常結束認證 * 因此在調用doFilter方法前添加password參數 */ class RequestParameterWrapper extends HttpServletRequestWrapper { private Map<String, String[]> params = new HashMap<String, String[]>(); public RequestParameterWrapper(HttpServletRequest request) { super(request); this.params.putAll(request.getParameterMap()); } public RequestParameterWrapper(HttpServletRequest request, Map<String, Object> extraParams) { this(request); addParameters(extraParams); } public void addParameters(Map<String, Object> extraParams) { for (Map.Entry<String, Object> entry : extraParams.entrySet()) { addParameter(entry.getKey(), entry.getValue()); } } @Override public String getParameter(String name) { String[]values = params.get(name); if(values == null || values.length == 0) { return null; } return values[0]; } @Override public String[] getParameterValues(String name) { return params.get(name); } @Override public Map<String, String[]> getParameterMap() { return params; } public void addParameter(String name, Object value) { if (value != null) { if (value instanceof String[]) { params.put(name, (String[]) value); } else if (value instanceof String) { params.put(name, new String[]{(String) value}); } else { params.put(name, new String[]{String.valueOf(value)}); } } } } }
@Data public class UserPojo implements Serializable { private Integer id; private String name; private String mobile; private String mail; private String pwd; public UserPojo() { } }
@Service public class IntegrationUserDetailsService implements UserDetailsService { private List<IntegrationAuthenticator> authenticators; @Autowired(required = false) public void setIntegrationAuthenticators(List<IntegrationAuthenticator> authenticators) { this.authenticators = authenticators; } @Override public UserDetails loadUserByUsername(String str) throws UsernameNotFoundException { IntegrationAuthenticationEntity entity = IntegrationAuthenticationContext.get(); if (entity == null){ entity = new IntegrationAuthenticationEntity(); } UserPojo pojo = this.authenticate(entity); if (pojo == null){ throw new UsernameNotFoundException("登陸失敗"); } User user = new User(pojo.getName(),pojo.getPwd(), AuthorityUtils.commaSeparatedStringToAuthorityList("ROOT_USER")); return user; } private UserPojo authenticate(IntegrationAuthenticationEntity entity) { if (this.authenticators != null) { for (IntegrationAuthenticator authenticator : authenticators) { if (authenticator.support(entity)) { return authenticator.authenticate(entity); } } } return null; } }
項目須要用到密碼模式因此將AuthenticationManager添加到容器中,不須要用到密碼模式,這步驟能夠跳過sql
@EnableWebSecurity @Configuration public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter { @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } }
@Configuration @EnableAuthorizationServer public class AuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter { @Autowired private AuthenticationManager authenticationManager; @Autowired private IntegrationUserDetailsService integrationUserDetailsService; //這裏true,使全局密碼結果爲true,由於有些登陸類型不須要驗證密碼,好比驗證碼登陸,第三方系統登陸等等,因此須要認證密碼的要單獨認證 @Bean public PasswordEncoder passwordEncoder(){ return new PasswordEncoder() { @Override public String encode(CharSequence charSequence) { return ""; } @Override public boolean matches(CharSequence charSequence, String s) { return true; } }; } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authenticationManager(authenticationManager) .userDetailsService(integrationUserDetailsService); } @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security.allowFormAuthenticationForClients() .tokenKeyAccess("isAuthenticated()") .checkTokenAccess("permitAll()"); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("client") .authorizedGrantTypes("password") .secret("server") .scopes("all"); } }
數據庫名:colue-auth,不是colue_auth數據庫
CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增主鍵', `name` varchar(100) NOT NULL COMMENT '暱稱', `mobile` varchar(100) NOT NULL COMMENT '手機號', `mail` varchar(100) NOT NULL COMMENT '郵箱', `pwd` varchar(100) NOT NULL COMMENT '密碼', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=83 DEFAULT CHARSET=utf8 COMMENT='用戶表'; INSERT INTO user VALUES(NULL,'root','13555555555','10086@qq.com','$2a$10$hcMi5tIUGGGim/Xe0Z7q4e5Zz3QlK.EAek3an3nZf0B.ZdN0GJgSe')
@Mapper public interface UserMapper { @Select("SELECT * FROM user WHERE name = #{name}") public UserPojo findByName(String name); @Select("SELECT * FROM user WHERE mobile = #{mobile}") public UserPojo findByMobile(String mobile); @Select("SELECT * FROM user WHERE mail = #{mail}") public UserPojo findByMail(String mail); }
@Component @Primary public class UsernamePasswordAuthenticator extends AbstractPreparableIntegrationAuthenticator { @Autowired private UserMapper mapper; @Override public UserPojo authenticate(IntegrationAuthenticationEntity entity) { String name = entity.getAuthParameter("name"); String pwd = entity.getAuthParameter("pwd"); if(name == null || pwd == null){ throw new OAuth2Exception("用戶名或密碼不能爲空"); } UserPojo pojo = mapper.findByName(name); BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); if(encoder != null && encoder.matches(pwd,pojo.getPwd())){ return pojo; } return null; } @Override public boolean support(IntegrationAuthenticationEntity entity) { return StringUtils.isEmpty(entity.getAuthType()); } }
Postman執行效果服務器
@Component public class SmsAuthenticator extends AbstractPreparableIntegrationAuthenticator { private final static String AUTH_TYPE = "sms"; @Autowired private UserMapper mapper; @Override public UserPojo authenticate(IntegrationAuthenticationEntity entity) { String mobile = entity.getAuthParameter("mobile"); if(StringUtils.isEmpty(mobile)){ throw new OAuth2Exception("手機號不能爲空"); } String code = entity.getAuthParameter("code"); //測試項目,因此將驗證碼頂死爲:1234 if(! "1234".equals(code)){ throw new OAuth2Exception("驗證碼錯誤或已過時"); } return mapper.findByMobile(mobile); } @Override public boolean support(IntegrationAuthenticationEntity entity) { return AUTH_TYPE.equals(entity.getAuthType()); } }
Postman執行效果app
1.流程思路:經過攔截器IntegrationAuthenticationFilter攔截全部oauth/token請求,根據類型參數(參數名:auth_type)匹配對應認證器(在全部繼承AbstractPreparableIntegrationAuthenticator類中調用support方法篩選),在匹配的成功的認證器調用authenticate方法執行用戶認證處理。
2.擴展其餘登陸方式只要實現自定義的IntegrationAuthenticator就行了。ide
3.項目源碼
https://gitee.com/yugu/cloud-...測試