说明:
启动mysql服务,启动一个eureka server端
一、工程配置:
1.导入security与oauth2等一些依赖包:
<dependencies>
<!--导入mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--导入web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--导入eureka客户端依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--导入security与oauth2依赖-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-data</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--导入JWT依赖-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
</dependencies>
2.配置认证证书和其他信息,在工程application.yml中:
server:
port: 10001
spring:
application:
name: token_server #指定获取token的服务名称
datasource: #配置mysql数据源(必须配置,不然报错)
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mydb?serverTimezone=UTC
username: root
password: root
eureka: #配置连接eureka服务端(必须配置,不然报错)
instance:
prefer-ip-address: true
client:
service-url:
defaultZone: http://127.0.0.1:10000/eureka/ #eureka server端地址,启动一个eureka server端
auth2: #自定义配置,用于AuthConfig类和LoginBiz类中获取
client-id: mallapp
client-secret: mallsecret
encrypt: #配置jks证书信息,自动读取
key-store:
location: classpath:/mall.jks #此证书为手动创建,放在resources目录下
secret: mallsecret
alias: mall
password: mall123456
3.创建SpringApplication启动类:
@SpringBootApplication
@EnableDiscoveryClient
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
@Bean(name = "restTemplate") //请求工具类
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
二、创建自动加载的配置类:
1.创建配置授权服务器端与客户端信息的类:
@Configuration
@EnableAuthorizationServer //自动加载,配置证书等信息
class AuthConfig extends AuthorizationServerConfigurerAdapter {
//配置证书
@Bean("key1")
public KeyProperties keyProperties() {
return new KeyProperties();
}
@Resource(name = "key1")
private KeyProperties keyProperties;
//配置Token转换类
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(AuthToMap authToMap) {
JwtAccessTokenConverter conver = new JwtAccessTokenConverter();
KeyPair key = new KeyStoreKeyFactory(keyProperties.getKeyStore().getLocation(), //配置证书路径
keyProperties.getKeyStore().getSecret().toCharArray()) //配置证书秘钥
.getKeyPair(keyProperties.getKeyStore().getAlias(), //配置证书别名
keyProperties.getKeyStore().getPassword().toCharArray()); //配置证书密码
conver.setKeyPair(key);
((DefaultAccessTokenConverter) conver.getAccessTokenConverter()).setUserTokenConverter(authToMap); //配置为自定义的AuthToMap转换类
return conver;
}
//配置token读/写存储类
@Autowired
private TokenStore tokenStore;
@Bean
@Autowired
public TokenStore tokenStore(JwtAccessTokenConverter conver) {
return new JwtTokenStore(conver);
}
//授权管理类
@Autowired
private AuthenticationManager authenticationManager;
//授权认证类,重写loadUserByUsername方法,用于封装为User返回
@Autowired
private UserDetailsService userDetailsService;
//自定义的AuthToMap转换类
@Autowired
private AuthToMap authToMap;
@Value("${auth2.client-id}") //application.yml中自定义字段,用于组装头字段
private String clientId;
@Value("${auth2.client-secret}") //application.yml中自定义字段,用于组装头字段
private String clientSecret;
@Override //配置客户端访问的一些信息
public void configure(ClientDetailsServiceConfigurer clientConfig) throws Exception {
clientConfig.inMemory()
.withClient(clientId) //配置客户端id,与application.yml一样
.secret(clientSecret) //配置秘钥,与application.yml一样
.redirectUris("http://www.baidu.com") //配置跳转地址
.accessTokenValiditySeconds(24 * 60 * 60) //配置token有效期
.refreshTokenValiditySeconds(24 * 60 * 60) //配置refreshToken有效期
.authorizedGrantTypes("authorization_code",//根据授权码生成令牌
"client_credentials", //客户端认证
"refresh_token", //刷新token
"password") //设置为密码认证方式
.scopes("app", "web"); //设置客户端范围
}
@Override //配置授权服务器端的一些信息
public void configure(AuthorizationServerEndpointsConfigurer serverConfig) throws Exception {
serverConfig.accessTokenConverter(jwtAccessTokenConverter) //配置Token转换类
.authenticationManager(authenticationManager) //配置授权管理类
.tokenStore(tokenStore) //配置token读写存储类
.userDetailsService(userDetailsService); //配置授权认证类,重写loadUserByUsername方法,用于封装为User返回
}
@Override//配置授权服务器的安全信息
public void configure(AuthorizationServerSecurityConfigurer serverSecurity) throws Exception {
serverSecurity.allowFormAuthenticationForClients()
.passwordEncoder(new BCryptPasswordEncoder()) //配置密码加密方式
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
}
2.创建配置验证规则类,设置不拦截的URL:
@EnableWebSecurity
@Order(-1)
@Configuration //配置验证规则,例外URL(自动加载)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override //配置不拦截的请求
public void configure(WebSecurity ws) throws Exception {
ws.ignoring().antMatchers("/login", "/logout"); //不拦截登录与登出的请求路径
}
@Override //配置验证规则
public void configure(HttpSecurity hs) throws Exception {
hs.csrf().disable().httpBasic() //使用HttpBasic认证:见HttpBasicConfigurer
.and().formLogin() //使用表单验证:见FormLoginConfigurer
.and().authorizeRequests() //Url授权配置器,请求除例外的URL需要权限:见ExpressionUrlAuthorizationConfigurer
.anyRequest() //任何请求:见AnyRequestMatcher
.authenticated(); //其他请求都需要经过验证
}
@Bean//加密工具类
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
}
}
3.创建信息转换类,将Auth转为Map返回:
@Component //信息转换类,将Auth转为Map返回
public class AuthToMap extends DefaultUserAuthenticationConverter {
@Autowired//授权认证类,重写loadUserByUsername方法,用于封装为User返回
UserDetailsService userDetailsService;
@Override //将Auth转为Map
public Map<String, ?> convertUserAuthentication(Authentication auth) {
User user = null; //org.springframework.security.core.userdetails.User
if (auth.getPrincipal() != null && auth.getPrincipal() instanceof User) {
user = (User) auth.getPrincipal();
} else {//调用自定义授权认证类,获取用户信息
user = (User) userDetailsService.loadUserByUsername(auth.getName());
}
//将信息封装为Map返回
Map<String, Object> map = new LinkedHashMap();
map.put("username", user.getUsername());
map.put("password", user.getPassword());
if (auth.getAuthorities() != null && !auth.getAuthorities().isEmpty()) {
map.put("authorities", AuthorityUtils.authorityListToSet(auth.getAuthorities()));
}
return map;
}
}
4.创建自定义授权认证类,重写loadUserByUsername方法,用于封装为User返回:
@Service //授权认证类,重写loadUserByUsername方法,用于封装为User返回
public class UserDetailsService implements org.springframework.security.core.userdetails.UserDetailsService {
@Autowired //org.springframework.security.oauth2.provider.ClientDetailsService;
private ClientDetailsService clientDetailsService;
@Override //重写loadUserByUsername方法,修改认证规则
public UserDetails loadUserByUsername(String phone) throws UsernameNotFoundException {
if (SecurityContextHolder.getContext().getAuthentication() == null) { //没有认证
ClientDetails details = clientDetailsService.loadClientByClientId(phone); //根据手机号加载client-secret信息
if (details != null) {
return new User(phone,
new BCryptPasswordEncoder().encode(details.getClientSecret()), //对client-secret加密
AuthorityUtils.commaSeparatedStringToAuthorityList(""));
}
}
//org.springframework.security.core.userdetails.User
return new User(phone,
new BCryptPasswordEncoder().encode("mall123456"), //对密码加密
AuthorityUtils.commaSeparatedStringToAuthorityList("user_info"));
}
}
三、使用oauth2实现登录返回token:
1.响应给客户端的json实体类:
//响应给客户端的json实体类
public class LoginInfo {
public String token; //访问其他接口时作为登录标识
public String refreshToken; //过期时刷薪token时用
public String jti; //身份标识
public LoginInfo(String token, String refreshToken, String jti) {
this.token = token;
this.refreshToken = refreshToken;
this.jti = jti;
}
}
2.登录业务类,请求授权服务,获取token:
@Service //登录业务类
public class LoginBiz {
@Autowired
private RestTemplate restTemplate;
@Autowired
private LoadBalancerClient loadBalancerClient;
@Value("${auth2.client-id}") //application.yml中自定义字段,用于组装头字段
private String clientId;
@Value("${auth2.client-secret}") //application.yml中自定义字段,用于组装头字段
private String clientSecret;
public LoginInfo login(String phone, String pwd) {
//1.创建请求头字段和参数
MultiValueMap<String, String> params = new LinkedMultiValueMap<>(); //MultiValueMap继承了Map
params.add("grant_type", "password"); //设置为密码授权方式
params.add("username", phone); //手机号
params.add("password", pwd); //登录密码
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
headers.add("Authorization", "Basic " + Base64.getEncoder().encodeToString((clientId + ":" + clientSecret).getBytes())); //Authorization认证
//2.处理400或401的情况
restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
@Override
public void handleError(ClientHttpResponse response) throws IOException {
if (response.getRawStatusCode() != 400 && response.getRawStatusCode() != 401) {
super.handleError(response);
}
}
});
String url = loadBalancerClient.choose("token_server").getUri().toString() + "/oauth/token";
System.out.println(" url: " + url);
//3.请求获取token信息
Map<String, String> body = restTemplate.exchange(
url, //获取token的服务地址,auth2_server为application.yml中的spring.application.name值
HttpMethod.POST, //POST请求方式
new HttpEntity<MultiValueMap>(params, headers), //设置请求参数和头字段
Map.class).getBody();
if (body == null) {
return null;
}
String token = body.get("access_token"); //访问其他接口时作为登录标识
String refresh_token = body.get("refresh_token"); //刷薪token时用
String jti = body.get("jti"); //身份标识
return new LoginInfo(token, refresh_token, jti);
}
}
3.登录请求拦截处理类:
@Controller //登录请求拦截处理类
public class LoginControl {
@Autowired //登录业务类
private LoginBiz loginBiz;
@RequestMapping("/login")
@ResponseBody
public LoginInfo login(String phone, String pwd) {
return loginBiz.login(phone, pwd);
}
}
4.请求登录接口查看效果(返回带token的json串表示成功了):
http://127.0.0.1:10001/login?phone=xxx&pwd=mall123456