实现/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-...测试