SpringBoot实现OAuth2认证服务器 |
您所在的位置:网站首页 › spring认证中心 › SpringBoot实现OAuth2认证服务器 |
一、最简单认证服务器
1. pom依赖
org.springframework.boot
spring-boot-starter-security
org.springframework.security.oauth.boot
spring-security-oauth2-autoconfigure
2.1.0.RELEASE
2. 配置application.yml
security:
oauth2:
client:
client-id: clientId
client-secret: clientSecret
scope: scope1, scope2, scope3, scope4
registered-redirect-uri: http://www.baidu.com
spring:
security:
user:
name: admin
password: admin
3. 开启@EnableAuthorizationServer,同时开启SpringSecurity用户登录认证
@SpringBootApplication
@EnableAuthorizationServer
public class SpringBootTestApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootTestApplication.class, args);
}
@Bean
public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter() {
return new WebSecurityConfigurerAdapter() {
@Override
public void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.formLogin().and().csrf().disable(); } }; }}
4. 测试
(1)密码模式和客户端模式直接通过单元测试就可以完成
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SpringBootTestApplicationTest {
@Autowired
private TestRestTemplate restTemplate;
@Test
public void token_password() {
MultiValueMap params = new LinkedMultiValueMap();
params.add("grant_type", "password");
params.add("username", "admin");
params.add("password", "admin");
params.add("scope", "scope1 scope2");
String response = restTemplate.withBasicAuth("clientId", "clientSecret").
postForObject("/oauth/token", params, String.class);
System.out.println(response);
}
@Test
public void token_client() {
MultiValueMap params = new LinkedMultiValueMap();
params.add("grant_type", "client_credentials");
String response = restTemplate.withBasicAuth("clientId", "clientSecret").
postForObject("/oauth/token", params, String.class);
System.out.println(response);
}
}
(2)授权码验证模式
访问 http://127.0.0.1:8080/oauth/authorize?client_id=clientId&response_type=code,跳转到SpringSecurity默认的登录页面:![]() 输入用户名/密码:admin/admin,点击登录后跳转到确认授权页面:
至少选中一个,然后点击Authorize按钮,跳转到 https://www.baidu.com/?code=tg0GDq,这样我们就拿到了授权码。 通过授权码申请token: @Test public void token_code() { MultiValueMap params = new LinkedMultiValueMap(); params.add("grant_type", "authorization_code"); params.add("code", "tg0GDq"); String response = restTemplate.withBasicAuth("clientId", "clientSecret").postForObject("/oauth/token", params, String.class); System.out.println(response); } (3)刷新token @Test public void token_refresh() { MultiValueMap params = new LinkedMultiValueMap(); params.add("grant_type", "refresh_token"); params.add("refresh_token", "fb00358a-44e2-4679-9129-1b96f52d8d5d"); String response = restTemplate.withBasicAuth("clientId", "clientSecret"). postForObject("/oauth/token", params, String.class); System.out.println(response); }刷新token功能报错,// todo 2018-11-08 此处留坑 二、比较复杂的认证服务器上面我们搭建的认证服务器存在以下弊端: clientId和clientSecret是写死在配置文件里的。 用户信息写死在配置文件里。 通过clientId和clientSecret获取的code和token都存在内存中。第一:如果服务器宕机code和token会丢失;第二:不支持多点部署。针对以上问题,我们要做的就是 将clientId和clientSecret等信息存储在数据库中。 将用户信息存储在数据库中。 将code和token存储在redis中。接下来我们一步一步实现: 1. 创建测试用表及数据![]() ![]() Dao和Service就不用废话了,肯定要有的 ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() 因为要使用到数据库以及redis,所以我们需要增加如下依赖: org.springframework.boot spring-boot-starter-jdbc org.springframework.boot spring-boot-starter-data-redis mysql mysql-connector-java 4. 修改启动主类,增加bean注册 (1)注册一个PasswordEncoder用于密码加密:这样做的目的是:在我们的应用中,可能都多个地方需要我们对用户的明文密码进行加密。在这里我们统一注册一个PasswordEncoder,以保证加密算法的一致性。 @Bean public PasswordEncoder passwordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } (2)注册一个UserDetailsService用于用户身份认证 @Bean public UserDetailsService userDetailsService(Oauth2Service oauth2Service, PasswordEncoder passwordEncoder) { return username -> { List users = oauth2Service.getOauth2UserByUsername(username); if (users == null || users.size() == 0) { throw new UsernameNotFoundException("username无效"); } Oauth2User user = users.get(0); String passwordAfterEncoder = passwordEncoder.encode(user.getPassword()); return User.withUsername(username).password(passwordAfterEncoder).roles("").build(); }; }标红这句代码大家忽略吧,常理来讲数据库中存储的密码应该就是密文所以这句代码是不需要的,我比较懒数据库直接存储明文密码所以这里需要加密一下。 (3)注册一个ClientDetailsService用户clientId和clientSecret验证 @Bean public ClientDetailsService clientDetailsService(Oauth2Service oauth2Service, PasswordEncoder passwordEncoder) { return clientId -> { List clients1 = oauth2Service.getOauth2ClientByClientId(clientId); if (clients1 == null || clients1.size() == 0) { throw new ClientRegistrationException("clientId无效"); } Oauth2Client client = clients1.get(0); String clientSecretAfterEncoder = passwordEncoder.encode(client.getClientSecret()); BaseClientDetails clientDetails = new BaseClientDetails(); clientDetails.setClientId(client.getClientId()); clientDetails.setClientSecret(clientSecretAfterEncoder); clientDetails.setRegisteredRedirectUri(new HashSet(Arrays.asList(client.getRedirectUrl().split(",")))); clientDetails.setAuthorizedGrantTypes(Arrays.asList(client.getGrantType().split(","))); clientDetails.setScope(Arrays.asList(client.getScope().split(","))); return clientDetails; }; }标红代码忽略,理由同上。 关于BaseClientDetails的属性,这里要啰嗦几句:它继承于接口ClientDetails,该接口包含如下属性: getClientId:clientId,唯一标识,不能为空 getClientSecret:clientSecret,密码 isSecretRequired:是否需要验证密码 getScope:可申请的授权范围 isScoped:是否需要验证授权范围 getResourceIds:允许访问的资源id,这个涉及到资源服务器 getAuthorizedGrantTypes:可使用的Oauth2授权模式,不能为空 getRegisteredRedirectUri:回调地址,用户在authorization_code模式下接收授权码code getAuthorities:授权,这个完全等同于SpringSecurity本身的授权 getAccessTokenValiditySeconds:access_token过期时间,单位秒。null等同于不过期 getRefreshTokenValiditySeconds:refresh_token过期时间,单位秒。null等同于getAccessTokenValiditySeconds,0或者无效数字等同于不过期 isAutoApprove:判断是否获得用户授权scope (4)注册一个TokenStore以保存token信息 @Bean public TokenStore tokenStore(RedisConnectionFactory redisConnectionFactory) { return new RedisTokenStore(redisConnectionFactory); } (5)注册一个AuthorizationCodeServices以保存authorization_code的授权码code生成一个RandomValueAuthorizationCodeServices的bean,而不是直接生成AuthorizationCodeServices的bean。RandomValueAuthorizationCodeServices可以帮我们完成code的生成过程。如果你想按照自己的规则生成授权码code请直接生成AuthorizationCodeServices的bean。 @Bean public AuthorizationCodeServices authorizationCodeServices(RedisConnectionFactory redisConnectionFactory) { RedisTemplate redisTemplate = new RedisTemplate(); redisTemplate.setConnectionFactory(redisConnectionFactory); redisTemplate.afterPropertiesSet(); return new RandomValueAuthorizationCodeServices() { @Override protected void store(String code, OAuth2Authentication authentication) { redisTemplate.boundValueOps(code).set(authentication, 10, TimeUnit.MINUTES); } @Override protected OAuth2Authentication remove(String code) { OAuth2Authentication authentication = redisTemplate.boundValueOps(code).get(); redisTemplate.delete(code); return authentication; } }; } (6)注册一个AuthenticationManager用来password模式下用户身份认证直接使用上面注册的UserDetailsService来完成用户身份认证。 @Bean public AuthenticationManager authenticationManager(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) { DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); provider.setUserDetailsService(userDetailsService); provider.setPasswordEncoder(passwordEncoder); return new ProviderManager(Collections.singletonList(provider)); } (7)配置认证服务器上面注册了这么多bean,到了他们发挥作用的时候了 @Bean public AuthorizationServerConfigurer authorizationServerConfigurer(UserDetailsService userDetailsService, ClientDetailsService clientDetailsService, TokenStore tokenStore, AuthorizationCodeServices authorizationCodeServices, AuthenticationManager authenticationManager) { return new AuthorizationServerConfigurer() { @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.withClientDetails(clientDetailsService); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.userDetailsService(userDetailsService); endpoints.tokenStore(tokenStore); endpoints.authorizationCodeServices(authorizationCodeServices); endpoints.authenticationManager(authenticationManager); } }; } 5. 修改配置文件,配置数据库及redis连接 spring: datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://192.168.2.12:3306/test?characterEncoding=utf8 username: root password: onceas redis: host: 192.168.2.12 port: 6379 password: 123456 6.测试 (1)密码模式和客户端模式同上 (2)授权码验证模式 访问 http://127.0.0.1:8080/oauth/authorize?client_id=clientId&response_type=code&scope=scope1 scope2&redirect_uri=http://www.baidu.com,跳转到SpringSecurity默认的登录页面:![]() 输入用户名/密码:admin/admin,点击登录后跳转到确认授权页面:
至少选中一个,然后点击Authorize按钮,跳转到 https://www.baidu.com/?code=tg0GDq,这样我们就拿到了授权码。 通过授权码申请token: @Test public void token_code() { MultiValueMap params = new LinkedMultiValueMap(); params.add("grant_type", "authorization_code"); params.add("code", "tg0GDq"); String response = restTemplate.withBasicAuth("clientId", "clientSecret").postForObject("/oauth/token", params, String.class); System.out.println(response); } (3)刷新token申请的所有token中都没有返回refresh_token,// todo 2018-11-08 此处留坑 三、自定义页面 1. 自定义用户登录页面用户登录页面就是SpringSecurity的默认登录页面,所以按照SpringSecurity的规则更改即可,可参照https://www.cnblogs.com/LOVE0612/p/9897647.html里面的相关内容 2. 自定义用户授权页面用户授权页面是/oauth/authorize转发给/oauth/confirm_access然后才呈现最终页面给用户的。所以想要自定义用户授权页面,用户点击Authorize按钮时会通过form表单发送请求: Request URL: http://127.0.0.1:8080/oauth/authorize Request Method: POST FormData user_oauth_approval: true scope.scope1: true scope.scope2: true所以我们要自定义用户授权页面,我们只要重新定义一个mapping即可并按照上述要求完成post请求即可。 (1)增加pom依赖 org.springframework.boot spring-boot-starter-thymeleaf (2)Controller @Controller public class Oauth2Controller { @GetMapping("oauth/confirm_access") public String authorizeGet() { return "oauth/confirm_access"; } } (3)创建/resources/templates/oauth/confirm_access.html DOCTYPE html> my authorize page function getQueryString(name) { var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i"); var r = window.location.search.substr(1).match(reg); if (r != null) return unescape(r[2]); return null; } var scope = getQueryString("scope"); var scopeList = scope.split(" "); var html = ""; for (var i = 0; i |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |