一、简介
1.1 什么是oauth2
OAuth(开放授权)是一个开放标准,允许用户授权第三方应用访问他们存储在另外的服务提供者上的信息, 而不 需要将用户名和密码提供给第三方应用或分享他们数据的所有内容。OAuth2.0是OAuth协议的延续版本, 但不向 后兼容OAuth 1.0即完全废止了OAuth1.0。很多大公司如Google,Yahoo,Microsoft等都提供了OAUTH认证服 务, 这些都足以说明OAUTH标准逐渐成为开放资源授权的标准
具体参考:tools.ietf.org/html/rfc674…
4种模式参考:掘金
注:今天重点不是介绍oauth2协议,主要介绍通过oauth2配置验证服务器
1.2 oauth2之AuthorizationServer
1)AuthorizationServer是一个接口,主要是用来统一验证服务的配置
2)具体实现类
二、AuthorizationServerConfigurerAdapter
主要通过实现 AuthorizationServerConfigurerAdapter类来完成验证服务器的配置
2.1 该类干什么的
AuthorizationServerConfigurerAdapter用来调度设置ClientDetailsServiceConfigurer、AuthorizationServerEndpointsConfigurer、AuthorizationServerSecurityConfigurer空壳类。由初始化时调用AuthorizationServerConfigurerAdapter.configure(xxxConfigure) 给机会开发注入、配置,如下图所示:
2.3 核心类介绍
1) AuthorizationServerSecurityConfigurer
用于客户端访问安全配置
2)ClientDetailsServiceConfigurer
用于配置client信息处理服务配置,具体可分为两种,一种将信息存储内存,一种存储jdbc,一般用存储内存的方法
//InMemoryClientDetailsServiceBuilder clients.inMemory() .withClient("coin-api") //clientid .secret(passwordEncoder.encode("coin-secret")) //client所用密码 .scopes("all") //授权范围 .authorizedGrantTypes("password","refresh_token") //授权类型 可以配置多个 .accessTokenValiditySeconds(24 * 7200) //失效时间 .refreshTokenValiditySeconds(7 * 24 * 7200) //刷新失效时间 //JdbcClientDetailsServiceBuilder clients.jdbc(new AbstractDataSource() { @Override public Connection getConnection() throws SQLException { return null; } .xxx
3)AuthorizationServerEndpointsConfigurer
访问端点配置,其实就是一个转载类,比如装载验证管理器(authenticationManager,注该类必须装配否则验证服务器将验证失败),用户授权信息(UserDetailsService)等,如下:
private AuthorizationServerTokenServices tokenServices; private ConsumerTokenServices consumerTokenServices; private AuthorizationCodeServices authorizationCodeServices; private ResourceServerTokenServices resourceTokenServices; private TokenStore tokenStore; private TokenEnhancer tokenEnhancer; private AccessTokenConverter accessTokenConverter; private ApprovalStore approvalStore; private TokenGranter tokenGranter; private OAuth2RequestFactory requestFactory; private OAuth2RequestValidator requestValidator; private UserApprovalHandler userApprovalHandler; private AuthenticationManager authenticationManager; private ClientDetailsService clientDetailsService;
2.3 实现
这里我主要通过我个人实现的配置流程来做一个说明(主要是通过密钥+用户名和密码来获取access_token即整合jwt的方式)
1)配置验证服务器
@EnableAuthorizationServer @Configuration public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter { @Autowired private PasswordEncoder passwordEncoder; //用于对密码进行加密 /******* *授权管理器 */ @Autowired private AuthenticationManager authenticationManager; /*********** *userDetailsService */ @Autowired private UserDetailsService userDetailsServiceipm; /********** * 第三方客户端 * @param clients * @throws Exception */ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("coin-api") .secret(passwordEncoder.encode("coin-secret")) .scopes("all") .authorizedGrantTypes("password","refresh_token") .accessTokenValiditySeconds(24 * 7200) //失效时间 .refreshTokenValiditySeconds(7 * 24 * 7200) //刷新失效时间 .and() //添加一个零时获取token的方式 .withClient("inside-app") .secret(passwordEncoder.encode("inside-secret")) .scopes("all") .authorizedGrantTypes("client_credentials") .accessTokenValiditySeconds(7 * 24 *3600); super.configure(clients); } /********** * 设置授权管理和userDetailsService * 采用token携带用户信息的方式(token+jwt) * @param endpoints * @throws Exception */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.tokenStore(jwtTokenStore()) //将信息存储jwt .tokenEnhancer(jwtAccessTokenConverter()) //序列化密钥信息 .authenticationManager(authenticationManager) .userDetailsService(userDetailsServiceipm); //UserDetailsService必须是注入容器中的 } /***** * 获取一个TokenStore * @return */ private TokenStore jwtTokenStore() { JwtTokenStore jwtTokenStore = new JwtTokenStore(jwtAccessTokenConverter()); return jwtTokenStore; } /************ * 将密钥对序列化 * @return */ private JwtAccessTokenConverter jwtAccessTokenConverter() { JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter(); //定义一个资源对象 ClassPathResource classPathResource = new ClassPathResource("coinexchange.jks"); KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(classPathResource, "coinexchange".toCharArray()); //创建一个密钥对 jwtAccessTokenConverter.setKeyPair( keyStoreKeyFactory.getKeyPair("coinexchange","coinexchange".toCharArray())); return jwtAccessTokenConverter; } }
获取私钥的方法:(其中 coinexchange为密码可以随便设置,coinexchange.jks为生成私钥的文件名)
keytool -genkeypair -alias coinexchange -keyalg RSA -keypass coinexchange -keystore coinexchange.jks -validity 365 -storepass coinexchange
解析出公钥:
keytool -list -rfc --keystore coinexchange.jks | openssl x509 -inform pem -pubkey //其中需要输入一个密码即为上述生成私钥的密码
2)WebSecurityConfig配置
*********** * web验证配置 */ @Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { /******** * 注入一个验证管理器 * @return * @throws Exception */ @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } /********** * 资源放行 * @param http * @throws Exception */ @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable();//关闭scrf http.authorizeRequests().anyRequest().authenticated(); } /*********** * 注入一个PasswordEncoder * @return */ @Bean public PasswordEncoder passwordEncoderBean(){ return new BCryptPasswordEncoder(); // return NoOpPasswordEncoder.getInstance(); } /******** * 测试用的,用来生成BCryptPasswordEncoder加密的字符串 * @param args */ public static void main(String[] args) { BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); System.out.println(bCryptPasswordEncoder.encode("123456")); } }
3)userDetailsServiceipm的注入(结合数据设置用户名和密码)
类图:
@Service public class UserDetailsServiceipm implements UserDetailsService { @Autowired private JdbcTemplate jdbcTemplate; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //获取上下文请求对象 ServletRequestAttributes requestAttributes =(ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); String login_type=requestAttributes.getRequest().getParameter("login_type"); if(StringUtils.isEmpty(login_type)){ throw new AuthenticationServiceException("登陆类型必选,请确认"); } String grant_type = requestAttributes.getRequest().getParameter("grant_type"); if(LoginConstant.GRANT_TYPE.equals(grant_type)){ //纠正username username=correctUsername(username,login_type); } //对管理员和会员进行操作 UserDetails userDetails=null; try { switch (login_type){ //管理员登录 case LoginConstant.ADMIN_TYPE: userDetails=LoadAdminUserByUsername(username); break; //会员登录 case LoginConstant.MEMBER_TYPE: userDetails=LoadMemberUserByUsername(username); break; default: throw new AuthenticationServiceException("不支持该登陆类型:"+login_type); } } catch (IncorrectResultSizeDataAccessException e) { System.out.println("该用户名不存在"); throw new UsernameNotFoundException("该用户名不存在"); } return userDetails; } } //userDetails=LoadAdminUserByUsername(username)方法 /********** * 加载管理员相关信息 * @param username * @return */ private UserDetails LoadAdminUserByUsername(String username) { //查询用户信息 return jdbcTemplate.queryForObject(LoginConstant.QUERY_SYS_USER_INFO_SQL, new RowMapper<User>() { @Override public User mapRow(ResultSet rs, int rowNum) throws SQLException { if(rs.wasNull()){ throw new UsernameNotFoundException("用户名:"+username+"不存在"); } //获取用户id long id = rs.getLong("id"); //获取用户密码 String password = rs.getString("password"); //获取用户状态 int status = rs.getInt("status"); User user = new User( String.valueOf(id), password, status == 1, true, true, true, getUserPermissions(id) ); return user; } },username); } }