文章篇幅较长,愿读者耐心看完。如有不足之处,请指正。
OAuth2是目前最流行的授权协议,用来授权第三方应用,获取用户数据。
举个例子:快递员想要进入小区,有3种方式。1是业主远程开门,2是业主告诉门禁密码,3是使用令牌(Oauth2)。
如图:
令牌和密码的区别:令牌相当于火车票,密码相当于是钥匙。
第三方登录演示(网易云客户端利用QQ扫码登录)
网易云客使用QQ扫码登录中Oauth2协议各个角色扮演者
Oauth2.0与1.0的区别:
Oauth2授权模式:
首先创建两个项目 一个是授权项目 一个是资源项目
授权项目目录(java-skill-point)
pom.xml文件
这里要注意springboot与springcloud的版本号
4.0.0 org.springframework.boot spring-boot-starter-parent 2.2.1.RELEASE com.weige java-skill-point 0.0.1-SNAPSHOT java-skill-point java-skill-point 1.8 Hoxton.SR5 org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-starter-web org.projectlombok lombok 1.18.22 cn.hutool hutool-all 5.7.20 org.springframework.cloud spring-cloud-starter-oauth2 org.springframework.cloud spring-cloud-starter-security org.springframework.cloud spring-cloud-dependencies ${spring-cloud.version} pom import org.springframework.boot spring-boot-maven-plugin
application.yml文件
server: port: 8888
OAuth2Config文件
package com.weige.javaskillpoint.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore; @Configuration @EnableAuthorizationServer public class OAuth2Config extends AuthorizationServerConfigurerAdapter { @Autowired private AuthenticationManager authenticationManager; private static final String CLIENT_ID = "cms"; private static final String SECRET_CHAR_SEQUENCE = "{noop}secret"; private static final String ALL = "all"; private static final int ACCESS_TOKEN_VALIDITY_SECONDS = 30 * 60; // 密码模式授权模式 private static final String GRANT_TYPE_PASSWORD = "password"; //授权码模式 private static final String AUTHORIZATION_CODE = "authorization_code"; //简化授权模式 private static final String IMPLICIT = "implicit"; //客户端模式 private static final String CLIENT_CREDENTIALS = "client_credentials"; @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients .inMemory() .withClient(CLIENT_ID) .secret(SECRET_CHAR_SEQUENCE) .autoApprove(false) .redirectUris("http://127.0.0.1:8084/cms/login") //重定向uri .scopes(ALL) .accessTokenValiditySeconds(ACCESS_TOKEN_VALIDITY_SECONDS) .authorizedGrantTypes(AUTHORIZATION_CODE, IMPLICIT, GRANT_TYPE_PASSWORD, CLIENT_CREDENTIALS); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authenticationManager(authenticationManager).tokenStore(memoryTokenStore()); } /** * 认证服务器的安全配置 * * @param security * @throws Exception */ @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security // 开启/oauth/check_token验证端口认证权限访问,checkTokenAccess("isAuthenticated()")设置授权访问 .checkTokenAccess("permitAll()") //允许表单认证 .allowFormAuthenticationForClients(); } @Bean public TokenStore memoryTokenStore() { return new InMemoryTokenStore(); } }
SecurityConfig文件
package com.weige.javaskillpoint.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @Configuration @EnableWebSecurity @Order(1) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //auth.inMemoryAuthentication() auth.inMemoryAuthentication() .withUser("lxs") .password("{noop}123") //使用springsecurity5,需要加上{noop}指定使用NoOpPasswordEncoder给DelegatingPasswordEncoder去校验密码 .roles("admin"); } @Override public void configure(WebSecurity web) throws Exception { //解决静态资源被拦截的问题 // web.ignoring().antMatchers("/asserts/**"); } @Override protected void configure(HttpSecurity http) throws Exception { http .formLogin().permitAll() .and().logout().logoutUrl("/logout").logoutSuccessUrl("/") .and().authorizeRequests().antMatchers("/oauth/**", "/login/**", "/logout/**", "/api/**").permitAll() .anyRequest().authenticated() // 关闭跨域保护; .and().csrf().disable(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } }
授权认证项目代码码完了 接下来给大家介绍代码的含义 重点是OAuth2Config与SecurityConfig两个类
OAuth2Config代码介绍:
SecurityConfig代码介绍:
资源项目目录(oauth2-service-01)
pom.xml文件
4.0.0 org.springframework.boot spring-boot-starter-parent 2.2.1.RELEASE com.weige java-skill-point 0.0.1-SNAPSHOT java-skill-point java-skill-point 1.8 Hoxton.SR5 org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-starter-web org.projectlombok lombok 1.18.22 cn.hutool hutool-all 5.7.20 org.springframework.cloud spring-cloud-starter-oauth2 org.springframework.cloud spring-cloud-starter-security org.springframework.cloud spring-cloud-dependencies ${spring-cloud.version} pom import org.springframework.boot spring-boot-maven-plugin
application.yml文件
server: port: 8084
Oauth2ResourceServerConfiguration文件
package com.weige.javaskillpoint.config; import java.io.IOException; import javax.annotation.Resource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpStatus; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter; import org.springframework.security.oauth2.provider.token.RemoteTokenServices; import org.springframework.web.client.DefaultResponseErrorHandler; import org.springframework.web.client.RestTemplate; @Configuration @EnableResourceServer public class Oauth2ResourceServerConfiguration extends ResourceServerConfigurerAdapter { private static final String CHECK_TOKEN_URL = "http://localhost:8888/oauth/check_token"; @Override public void configure(ResourceServerSecurityConfigurer resources) { RemoteTokenServices tokenService = new RemoteTokenServices(); tokenService.setCheckTokenEndpointUrl(CHECK_TOKEN_URL); tokenService.setClientId("cms"); tokenService.setClientSecret("secret"); resources.tokenServices(tokenService); } }
SecurityConfiguration文件
package com.weige.javaskillpoint.config; 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.WebSecurityConfigurerAdapter; @EnableWebSecurity public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests().antMatchers("/**").authenticated(); // 禁用CSRF http.csrf().disable(); } }
HelloController文件
package com.weige.javaskillpoint.controller; import org.springframework.security.access.annotation.Secured; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Arrays; @RestController @RequestMapping("/cms") public class HelloController { @GetMapping("/getCurrentUser") public Object getCurrentUser(Authentication authentication) { return authentication; } @GetMapping("/index") public String index() { return "index"; } @PutMapping("/index2") public String index2() { return "index2"; } }
资源项目代码码完了 接下来给大家介绍代码的含义 重点是Oauth2ResourceServerConfiguration与SecurityConfiguration两个类
Oauth2ResourceServerConfiguration代码介绍:
SecurityConfiguration代码介绍:
HelloController类中定义了受限访问的资源
启动授权服务项目,结果演示:
测试:
随便校验一个令牌:校验接口 http://localhost:8888/oauth/check_token?token=abcdefg123456
启动资源服务项目,访问接口:
没有携带token,访问无权限
授权码模式:
1.申请授权码 localhost:8888/oauth/authorize?client_id=cms&cliect_secret=secret&response_type=code
2.根据授权码申请令牌(在PostMan中调用接口):http://localhost:8888/oauth/token?code=ckujfQ&grant_type=authorization_code&redirect_url=http://127.0.0.1:8084/cms/login&scope=all
令牌校验:http://localhost:8888/oauth/check_token?token=4a7e8dbc-24ef-4517-8de3-d9b0229dfa0b
使用令牌:http://localhost:8888/oauth/check_token?token=4a7e8dbc-24ef-4517-8de3-d9b0229dfa0b
携带token则访问资源项目接口成功
简化模式:
申请令牌 浏览器请求:localhost:8888/oauth/authorize?client_id=cms&redirect_uri=http://127.0.0.1:8084/cms/login&response_type=token&scope=all
输入网址回车
校验令牌:http://localhost:8888/oauth/check_token?token=f9013048-946b-4875-a83a-69371e7e0829
资源测试
密码模式:
申请令牌 使用PostMan:localhost:8888/oauth/token?grant_type=password&username=lxs&password=123&scope=all
校验令牌:http://localhost:8888/oauth/check_token?token=1365345d-8765-47d4-8d2f-563a31a8b3a7
资源测试
客户端模式:
申请令牌 使用PostMan:localhost:8888/oauth/token?grant_type=password&username=lxs&password=123&scope=all
校验令牌:http://localhost:8888/oauth/check_token?token=5c58a1ef-e1d5-49e7-9ef7-da1063261998
资源测试
1. ⽣成密钥证书 下边命令⽣成密钥证书,采⽤RSA 算法每个证书包含公钥和私钥 创建⼀个⽂件夹,在该⽂件夹下执⾏如下命令⾏:
keytool -genkeypair -alias kaikeba -keyalg RSA -keypass kaikeba -keystore kaikeba.jks -storepass kaikeb
参数解释:
导出公钥
下载网址 https://slproweb.com/products/Win32OpenSSL.html
配置环境变量
cmd进⼊kaikeba.jks⽂件所在⽬录执⾏如下命令 keytool -list -rfc --keystore kaikeba.jks | openssl x509 -inform pem -pubkey
下⾯段内容是公钥
-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAojlMgqf4RVL0ExMpJWoO q35eow0lhDFTpUOUkU1ZGWoK+ZwQFUnR5w5U+u2mHqq3dFJWUIAmQC+y5p2ClsvG TbAvYQmL1k4X6oaGj7Bi9SSX6QQeM5bXsrXsjsGJyQyIbqgyBYIg4ZNB29UDcTCv xdFl8+rXoOppqENnTZpij8EIzJooCfrc2GzAeljmgPi4DFJnDAxE4joVz70xWk36 noRHSEfvmUaW+1S1T0cEH+j9p8PUGonnjqU8R6ZPmKAhU1w2t002dLqtkFDzxPW7 M1uJ4hq/CL7smvkOkGb0UVLAwFR9hzO2loxn/y0DcRdyxb5FVPQXnjXCp2hyT0mX pwIDAQAB -----END PUBLIC KEY-----
创建public.key文件 将公钥复制进去
搭建授权项目 项目目录
pom.xml
4.0.0 org.springframework.boot spring-boot-starter-parent 2.2.1.RELEASE com.weige java-skill-point 0.0.1-SNAPSHOT java-skill-point java-skill-point 1.8 Hoxton.SR5 org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-starter-web org.projectlombok lombok 1.18.22 mysql mysql-connector-java runtime org.mybatis mybatis 3.4.6 org.mybatis.spring.boot mybatis-spring-boot-starter 2.2.2 cn.hutool hutool-all 5.7.20 org.apache.commons commons-lang3 3.7 org.springframework.cloud spring-cloud-starter-oauth2 org.springframework.cloud spring-cloud-starter-security org.springframework.cloud spring-cloud-dependencies ${spring-cloud.version} pom import org.springframework.boot spring-boot-maven-plugin
application.yml
server: port: 8888 spring: main: allow-bean-definition-overriding: true # mysql连接信息 datasource: # mysql8之后 driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://43.143.132.109:3306/index?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT username: root password: ******
大家在这里要创建对应的数据库index 并创建oauth_client_details表
CREATE TABLE `oauth_client_details` ( `client_id` varchar(256) COLLATE utf8mb4_german2_ci NOT NULL, `resource_ids` varchar(256) COLLATE utf8mb4_german2_ci DEFAULT NULL, `client_secret` varchar(256) COLLATE utf8mb4_german2_ci DEFAULT NULL, `scope` varchar(256) COLLATE utf8mb4_german2_ci DEFAULT NULL, `authorized_grant_types` varchar(256) COLLATE utf8mb4_german2_ci DEFAULT NULL, `web_server_redirect_uri` varchar(256) COLLATE utf8mb4_german2_ci DEFAULT NULL, `authorities` varchar(256) COLLATE utf8mb4_german2_ci DEFAULT NULL, `access_token_validity` int(11) DEFAULT NULL, `refresh_token_validity` int(11) DEFAULT NULL, `additional_information` varchar(4096) COLLATE utf8mb4_german2_ci DEFAULT NULL, `autoapprove` varchar(256) COLLATE utf8mb4_german2_ci DEFAULT NULL, PRIMARY KEY (`client_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_german2_ci; INSERT INTO `index`.`oauth_client_details` (`client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `autoapprove`) VALUES ('cms', '', 'a$WZQaLHfS6amrJzN50wE3e.upn8KIi1wmCH9FSdZE6OBt8OKSyGLm.', 'read, write', 'client_credentials,implicit,authorization_code,refresh_token,password', 'http://www.baidu.com', NULL, NULL, NULL, NULL, 'false'); ## 'a$WZQaLHfS6amrJzN50wE3e.upn8KIi1wmCH9FSdZE6OBt8OKSyGLm.' 是加密过后的123456
AdminTokenConstant文件
package com.weige.javaskillpoint.config; public class AdminTokenConstant { /** * 秘钥全名称 */ public static final String KEY_LOCATION = "kaikeba.jks"; /** * 密钥的密码,此密码和别名要匹配 */ public static final String KEY_PASSWORD = "kaikeba"; }
AuthorizationServerConfiguration文件
package com.weige.javaskillpoint.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; import org.springframework.security.oauth2.provider.ClientDetailsService; import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService; import org.springframework.security.oauth2.provider.token.DefaultTokenServices; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory; import javax.annotation.Resource; import javax.sql.DataSource; import java.util.concurrent.TimeUnit; @Configuration @EnableAuthorizationServer public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter { @Resource private AuthenticationManager authenticationManager; @Resource private DataSource dataSource; @Bean public TokenStore tokenStore() { return new JwtTokenStore(jwtAccessTokenConverter()); } @Bean public JwtAccessTokenConverter jwtAccessTokenConverter() { // 证书路径和密钥库密码 KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource(AdminTokenConstant.KEY_LOCATION), AdminTokenConstant.KEY_PASSWORD.toCharArray()); JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); // 密钥别名 converter.setKeyPair(keyStoreKeyFactory.getKeyPair(AdminTokenConstant.KEY_PASSWORD)); return converter; } /** * 声明 ClientDetails实现 * * @return */ @Bean public ClientDetailsService clientDetailsService() { return new JdbcClientDetailsService(dataSource); } /** * 配置客户端详情服务(ClientDetailsService),客户端详情信息在这里进行初始化, * 你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息 * * @param clients * @throws Exception */ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { // 直接读取数据库,需要保证数据库配置有客户端信息(oauth_client_details),否则资源服务器无法获取认证数据 clients.withClientDetails(clientDetailsService()); } /** * 配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services),还有token的存储方式(tokenStore) * * @param endpoints */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) { endpoints.tokenStore(tokenStore()).tokenEnhancer(jwtAccessTokenConverter()).authenticationManager(authenticationManager); // 配置tokenServices参数 DefaultTokenServices tokenServices = new DefaultTokenServices(); tokenServices.setTokenStore(endpoints.getTokenStore()); tokenServices.setSupportRefreshToken(false); tokenServices.setClientDetailsService(endpoints.getClientDetailsService()); tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer()); tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(30)); endpoints.tokenServices(tokenServices); } @Override public void configure(AuthorizationServerSecurityConfigurer security) { // 允许表单认证 security.allowFormAuthenticationForClients() // 开启/oauth/token_key验证端口无权限访问 .tokenKeyAccess("permitAll()") // 开启/oauth/check_token验证端口认证权限访问 .checkTokenAccess("permitAll()"); } }
SecurityConfiguration文件
package com.weige.javaskillpoint.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 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.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import javax.annotation.Resource; @Configuration @EnableWebSecurity public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Resource private UserDetailsService userDetailsService; @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } /** * @param http * @throws Exception */ @Override protected void configure(HttpSecurity http) throws Exception { // 所有请求都加入HttpSecurity(多个HttpSecurity过滤) http.requestMatchers().anyRequest() // 开放/oauth/开头的所有请求 .and().authorizeRequests().antMatchers("/oauth/**").permitAll(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // 注入自定义的UserDetailsService,采用BCrypt加密 auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } }
UserDetailServiceImpl文件
package com.weige.javaskillpoint.service.impl; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @Slf4j @Service public class UserDetailServiceImpl implements UserDetailsService { /** * 根据用户名加载用户信息 * * @param username 用户名 * @return 用户详情 * @throws UsernameNotFoundException */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 这里可以连接数据库 我这里写假数据 // 有两个用户(username:user,password:user;username:admin,password:admin) Mapmap = new HashMap<>(); map.put("user", "USER"); map.put("admin", "ADMIN"); String passWord = "$2a$10$b7cqpfkMLYE2H5wxMluGQOrXo76ZfaAALEaemrfFLwDvvHuJuvX/2"; if (username.equals("admin")) { passWord = "$2a$10$b7cqpfkMLYE2H5wxMluGQOrXo76ZfaAALEaemrfFLwDvvHuJuvX/2"; } List grantedAuthorities = new ArrayList<>(); // 获取用户的授权 String s = map.get(username); // Spring Security 中权限名称必须满足ROLE_XXX GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(StringUtils.join("ROLE_", s)); grantedAuthorities.add(grantedAuthority); log.info("granted authorities :{} ", grantedAuthorities); return new User(username, passWord, grantedAuthorities); } }
JavaSkillPointApplication文件
package com.weige.javaskillpoint; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication @MapperScan("com.weige.javaskillpoint.dao") public class JavaSkillPointApplication { public static void main(String[] args) { SpringApplication.run(JavaSkillPointApplication.class, args); } }
JavaSkillPointApplicationTests测试文件
package com.weige.javaskillpoint; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.core.io.ClassPathResource; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.jwt.Jwt; import org.springframework.security.jwt.JwtHelper; import org.springframework.security.jwt.crypto.sign.RsaSigner; import org.springframework.security.jwt.crypto.sign.RsaVerifier; import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory; import java.security.KeyPair; import java.security.interfaces.RSAPrivateKey; import java.util.HashMap; import java.util.Map; @SpringBootTest class JavaSkillPointApplicationTests { @Test public void testCreateJwt() throws Exception { //证书⽂件 String key_location = "kaikeba.jks"; // 密钥库密码 String keystore_password = "kaikeba"; // 访问证书路径 ClassPathResource resource = new ClassPathResource(key_location); // 密钥⼯⼚ KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(resource, keystore_password.toCharArray()); // 密钥的密码,此密码和别名要匹配 String keypassword = "kaikeba"; //密钥别名 String alias = "kaikeba"; // 密钥对(密钥和公钥) KeyPair keyPair = keyStoreKeyFactory.getKeyPair(alias, keypassword.toCharArray()); // 私钥 RSAPrivateKey aPrivate = (RSAPrivateKey) keyPair.getPrivate(); // 定义payload信息 MaptokenMap = new HashMap (); tokenMap.put("id", "123"); tokenMap.put("name", "malong"); tokenMap.put("roles", "r01,r02"); tokenMap.put("ext", "1"); // ⽣成jwt令牌 Jwt jwt = JwtHelper.encode(new ObjectMapper().writeValueAsString(tokenMap), new RsaSigner(aPrivate)); // 取出jwt令牌 String token = jwt.getEncoded(); System.out.println(token); } @Test public void testVerify() { //jwt令牌 String token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHQiOiIxIiwicm9sZXMiOiJyMDEscjAyIiwibmFtZSI6Im1hbG9uZyIsImlkIjoiMTIzIn0.X68eLdVW69xJQGcICVZapWUay-vs5Fkv0cYRQoymuagR95G4bOkJ4d3rer6ko9gMS55htLmAmv3HkzeCOOE4R6fXJcGzyuDWY9D9lC7ca0-AX4okS4iLeTQAf53AIDLM3d1DQbRdJYrdDbSwhXZXIaaaaQNiVpnN3kGXK6YB7f1ohlEURFT50bf7lKVyc8xoJX4-ojLfZiWP2C8Ov84yEOc-Q2t9kRwPtPqQjial69b0FmPqtdfPbhJG66NAQdikRCxqHKSgb6QSMc9AF9AV4RluaPixoGIpufouWpWXyk9PvK-QJwwUJgM11emDTX2wv8lf5VXH-Wdg_1Jc_uwjLA"; String publickey = "-----BEGIN PUBLIC KEY-----\n" + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlym6EoAWxPYpTQHuWfqn\n" + "WqRkiUDrD+CyXM3Rzgtfi6ofudPDyEY5JbFcKIcfwjdQo+VLF0dIvyJA2csHhQIS\n" + "vZBspsE9cjz24GOuYSYufYtwOVcbjsvZ3JQaJahR6rBXzPTjvaiu1KtFtEJavWQ8\n" + "S9nhZ4MEMImW8r+Qphd0R4OB8KHrWoztYNznWpSaaH3QB446tQyyBre7tEPp3E6J\n" + "Xq+ApI5UKBpTdHW7b7MSJW836sYm8g5XfaXF667rlkdS03q3B182hUcghRY+fAyS\n" + "OtD2A0ib7XlrNADPL9X3dazWCYTnbNDswiqfpoDrbLy0PWFeIq09amL+E1ivKJZS\n" + "9wIDAQAB\n" + "-----END PUBLIC KEY-----"; //校验jwt Jwt jwt = JwtHelper.decodeAndVerify(token, new RsaVerifier(publickey)); //获取jwt原始内容 String claims = jwt.getClaims(); System.out.println(claims); } @Test public void getEnPassword(){ String encode = new BCryptPasswordEncoder().encode("123456"); System.out.println(encode); } }
这里别忘记了生成的JWT公钥私钥文件 放到reources目录下即可
搭建资源项目 项目目录
pom.xml
4.0.0 org.springframework.boot spring-boot-starter-parent 2.2.1.RELEASE com.weige java-skill-point 0.0.1-SNAPSHOT java-skill-point java-skill-point 1.8 Hoxton.SR5 org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-starter-web org.projectlombok lombok 1.18.22 org.apache.commons commons-lang3 3.7 mysql mysql-connector-java runtime org.mybatis mybatis 3.4.6 org.mybatis.spring.boot omybatis-spring-boot-starter 2.2.2 cn.hutool hutool-all 5.7.20 org.springframework.cloud spring-cloud-starter-oauth2 org.springframework.cloud spring-cloud-starter-security org.springframework.cloud spring-cloud-dependencies ${spring-cloud.version} pom import org.springframework.boot spring-boot-maven-plugin
application.yml
server: port: 8084 spring: # mysql连接信息 datasource: # mysql8之后 driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://43.143.132.109:3306/index?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT username: root password: Weikai19991015. security: oauth2: resource: jwt: key-uri: http://localhost:8888/oauth/token_key
Config文件
package com.weige.javaskillpoint.config; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @Configuration public class Config { @Bean public RestTemplate restTemplate(RestTemplateBuilder builder) { return builder.build(); } }
JwtConfig文件
package com.weige.javaskillpoint.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; import org.springframework.util.FileCopyUtils; import java.io.IOException; @Configuration public class JwtConfig { public static final String public_cert = "public.key"; @Autowired private JwtAccessTokenConverter jwtAccessTokenConverter; @Bean @Qualifier("tokenStore") public TokenStore tokenStore() { return new JwtTokenStore(jwtAccessTokenConverter); } @Bean protected JwtAccessTokenConverter jwtAccessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); Resource resource = new ClassPathResource(public_cert); String publicKey; try { publicKey = new String(FileCopyUtils.copyToByteArray(resource.getInputStream())); } catch (IOException e) { throw new RuntimeException(e); } // 设置校验公钥 converter.setVerifierKey(publicKey); // 设置证书签名密码,否则报错 converter.setSigningKey("kaikeba"); return converter; } }
ResourceServerConfiguration文件
package com.weige.javaskillpoint.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; import org.springframework.security.oauth2.provider.token.TokenStore; @Configuration @EnableResourceServer @EnableGlobalMethodSecurity(prePostEnabled = true) public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter { @Autowired private TokenStore tokenStore; @Override public void configure(HttpSecurity http) throws Exception { http.csrf().disable().authorizeRequests() .antMatchers("/user/**") .permitAll() .antMatchers("/book/**").hasRole("admin") .antMatchers("/**") .authenticated(); } @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { resources.tokenStore(tokenStore); } }
HelloController文件
package com.weige.javaskillpoint.controller; import org.springframework.security.access.annotation.Secured; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Arrays; @RestController @RequestMapping("/cms") public class HelloController { @GetMapping("/getCurrentUser") public Object getCurrentUser(Authentication authentication) { return authentication; } @GetMapping("/index/user") @PreAuthorize("hasRole('ROLE_USER')") public String index() { return "index/user"; } @GetMapping("/index/admin") @PreAuthorize("hasRole('ROLE_ADMIN')") public String index2() { return "index/admin"; } }
JavaSkillPointApplication文件
package com.weige.javaskillpoint; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication @MapperScan("com.weige.javaskillpoint.dao") public class JavaSkillPointApplication { public static void main(String[] args) { SpringApplication.run(JavaSkillPointApplication.class, args); } }
这里别忘记了生成的JWT公钥文件 放到reources目录下即可
两个项目都创建完成,在启动项目时。要注意先后顺序,先启动授权项目,在启动资源项目。因为资源项目会请求授权项目中的token中JTW对应的公钥解析是否对应私钥。
密码模式:
申请令牌 PostMan请求 localhost:8888/oauth/token?grant_type=password&username=user&password=123456&scope=read
资源项目接口测试:
资源服务器接口调用 USER http://localhost:8084/cms/index/user
资源服务器接口调用 ADMIN http://localhost:8084/cms/index/admin
资源服务器接口调用 ADMIN http://localhost:8084/cms/index/admin
资源项目添加几个类:
AdminConstants文件
package com.weige.javaskillpoint.constant; public interface AdminConstants { /** * 请求头key */ String AUTHORIZATION_KEY = "Authorization"; /** * Basic */ String BASIC_KEY = "Basic "; /** * 用户名key */ String USERNAME_KEY = "username"; /** * 密码key */ String PASSWORD_KEY = "password"; /** * 认证类型key */ String GRANT_TYPE_KEY = "grant_type"; /** * 授权范围key */ String SCOPE_KEY = "scope"; /** * 分隔符 */ String SPLIT = ":"; }
LoginController文件
package com.weige.javaskillpoint.controller; import com.weige.javaskillpoint.entity.LoginRequestDTO; import com.weige.javaskillpoint.service.LoginService; import org.springframework.http.ResponseEntity; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; @RestController @RequestMapping("/user") public class LoginController { @Resource private LoginService loginService; /** * 用户登录 * * @param loginRequestDTO 登录请求DTO * @return org.springframework.http.ResponseEntity */ @PostMapping("/login") public ResponseEntitylogin(@RequestBody LoginRequestDTO loginRequestDTO) { return loginService.login(loginRequestDTO); } }
LoginRequestDTO文件
package com.weige.javaskillpoint.entity; import lombok.Getter; import lombok.Setter; import lombok.ToString; @Setter @Getter @ToString public class LoginRequestDTO { /** * 用户名 */ private String username; /** * 密码 */ private String password; }
LoginService文件
package com.weige.javaskillpoint.service; import com.weige.javaskillpoint.entity.LoginRequestDTO; import org.springframework.http.ResponseEntity; import org.springframework.security.oauth2.common.OAuth2AccessToken; public interface LoginService { /** * 用户登录 * * @param loginRequestDTO 登录请求DTO * @return org.springframework.http.ResponseEntity */ ResponseEntitylogin(LoginRequestDTO loginRequestDTO); }
LoginServiceImpl文件
package com.weige.javaskillpoint.service.impl; import com.weige.javaskillpoint.constant.AdminConstants; import com.weige.javaskillpoint.entity.LoginRequestDTO; import com.weige.javaskillpoint.service.LoginService; import org.apache.commons.lang3.StringUtils; import org.springframework.boot.autoconfigure.security.oauth2.OAuth2ClientProperties; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.stereotype.Service; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; import javax.annotation.Resource; import java.util.Base64; import java.util.Collections; @Service public class LoginServiceImpl implements LoginService { @Resource private RestTemplate restTemplate; @Resource private OAuth2ClientProperties oAuth2ClientProperties; @Resource private OAuth2ProtectedResourceDetails oAuth2ProtectedResourceDetails; /** * 用户登录 * * @param loginRequestDTO 登录请求DTO * @return org.springframework.http.ResponseEntity */ @Override public ResponseEntitylogin(LoginRequestDTO loginRequestDTO) { HttpHeaders headers = new HttpHeaders(); headers.set(AdminConstants.AUTHORIZATION_KEY, StringUtils.join(AdminConstants.BASIC_KEY, Base64.getEncoder().encodeToString(StringUtils.join(oAuth2ClientProperties.getClientId(), AdminConstants.SPLIT, oAuth2ClientProperties.getClientSecret()).getBytes()))); // 组装请求参数 MultiValueMap map = new LinkedMultiValueMap<>(4); map.put(AdminConstants.USERNAME_KEY, Collections.singletonList(loginRequestDTO.getUsername())); map.put(AdminConstants.PASSWORD_KEY, Collections.singletonList(loginRequestDTO.getPassword())); map.put(AdminConstants.GRANT_TYPE_KEY, Collections.singletonList(oAuth2ProtectedResourceDetails.getGrantType())); map.put(AdminConstants.SCOPE_KEY, oAuth2ProtectedResourceDetails.getScope()); // 请求到授权服务器,将授权完的用户信息存到授权服务器,并申请令牌 return restTemplate.exchange(oAuth2ProtectedResourceDetails.getAccessTokenUri(), HttpMethod.POST, new HttpEntity(map, headers), OAuth2AccessToken.class); } }
application.yml文件
server: port: 8084 spring: # mysql连接信息 datasource: # mysql8之后 driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://43.143.132.109:3306/index?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT username: root password: Weikai19991015. security: oauth2: resource: jwt: key-uri: http://localhost:8888/oauth/token_key client: access-token-uri: http://localhost:8888/oauth/token #令牌端点 user-authorization-uri: http://localhost:8888/oauth/authorize #授权端点 client-id: cms client-secret: 123456 grant-type: password scope: read,write
调用资源服务登录接口:PostMan调用 http://localhost:8084/user/login
拿到获取的token访问资源服务的接口 http://localhost:8084/cms/index/user
用admin登录
拿到获取的token访问资源服务的接口 http://localhost:8084/cms/index/admin
完结撒花 !