feign 默认不携带 token,需要进行配置:
hystrix:
command:
default:
execution:
isolation:
strategy: SEMAPHORE #设置hysitrix的隔离级别为信号量,只有这个级别才可以拿到客户端发送的token,默认级别是THREAD
package com.zhiyou100.config;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
/**feign使用的时候通过Service获取别的服务器中的数据,出现token丢失的问题,
* 为了解决这个问题,我们创建这个配置类,并且在dev中添加配置
* @author zhangfan
* @date 2019/10/21
*/
@Configuration
public class MyRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
//获取 客户端的授权信息
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
//只有客户端传递token的时候才去添加
if (authentication != null){
//获取token详细信息
Object details = authentication.getDetails();
if (details instanceof OAuth2AuthenticationDetails){
OAuth2AuthenticationDetails details1 = (OAuth2AuthenticationDetails) details;
//获取token
String token = details1.getTokenValue();
//在feign的请求头中添加token
requestTemplate.header("Authorization","bearer" + token);
}
}
}
}
修改 auth 服务,从数据库读取和验证用户信息:
1. 制作数据库
需要 3 张表:用户表,角色表和用户角色表(关系表)
DROP DATABASE IF EXISTS `spring_cloud_01`;
CREATE DATABASE `spring_cloud_01`;
USE `spring_cloud_01`;
CREATE TABLE IF NOT EXISTS `user` (
`id` INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
`account` VARCHAR(20) NOT NULL COMMENT '账号',
`password` VARCHAR(100) NOT NULL COMMENT '密码'
) COMMENT '用户表';
INSERT INTO user VALUES(NULL, 'zhangsan', '{bcrypt}$2a$10$YQjMpUJ116gDNBVKntYnJewx4/mdhLar38VBYR/5DX7mYTXw/cPcm');
INSERT INTO user VALUES(NULL, 'lisi', '{bcrypt}$2a$10$YQjMpUJ116gDNBVKntYnJewx4/mdhLar38VBYR/5DX7mYTXw/cPcm');
CREATE TABLE IF NOT EXISTS `role` (
`id` INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
`name` VARCHAR(20) NOT NULL COMMENT '名称'
) COMMENT '角色表';
INSERT INTO role VALUES(NULL, 'ADMIN');
INSERT INTO role VALUES(NULL, 'EMPLOYEE');
CREATE TABLE IF NOT EXISTS `user_role` (
`id` INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
`user_id` INT NOT NULL COMMENT '用户主键',
`role_id` INT NOT NULL COMMENT '角色主键'
) COMMENT '用户角色表';
INSERT INTO user_role VALUES(NULL, 1, 1);
INSERT INTO user_role VALUES(NULL, 2, 2);
2. 在auth中编写 User 和 Role 实体类
在 User 中增加 private List roles; 属性
表示用户拥有的所有角色
前提:导入依赖
<!--lombok依赖,自动生成实体类中的get set 构造方法-->
org.projectlombok
lombok
true
<!--mysql依赖-->
mysql
mysql-connector-java
runtime
<!--MyBatis-Plus (简称 MP)是一个 MyBatis 的增强工具,
在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。-->
com.baomidou
mybatis-plus-boot-starter
3.2.0
com.baomidou
mybatis-plus
3.2.0
<!--测试依赖-->
org.springframework.boot
spring-boot-starter-test
test
3. Role 类实现 GrantedAuthority 接口
getAuthority() 方法返回角色名字(name 属性的值)
package com.zhiyou100.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
/**
* @author zhangfan
* @date 2019/10/21
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("name = role")
public class Role implements GrantedAuthority {
@TableId(type = IdType.AUTO)
private Integer id;
private String name;
//返回角色的名字
@Override
public String getAuthority() {
return name;
}
}
4. User 类实现 UserDetails 接口
getAuthorities() 方法返回用户拥有的角色(roles 属性的值)
getUsername() 方法返回用户账号(account 属性的值)
getPassword() 方法返回用户密码(password 属性的值)
isAccountNonExpired() 方法返回账号 是否 没有过期(true)
isAccountNonLocked() 方法返回账号 是否 没有被锁定(true)
isCredentialsNonExpired() 方法返回账号 是否 没有过期(true)
isEnabled() 方法返回账号 是否 启用(true)
package com.zhiyou100.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
/**
* @author zhangfan
* @date 2019/10/21
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("user")
public class User implements UserDetails {
@TableId(type = IdType.AUTO)
private Integer id;
private String account;
private String password;
//在user中增加属性,表示用户拥有的所有角色
private List<Role> roles;
//返回用户拥有的角色
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles;
}
//返回用户账号
@Override
public String getUsername() {
return account;
}
//getPassword()方法被Date注解吃了,就不用我们返回了
//返回账号是否没有过期:是true
@Override
public boolean isAccountNonExpired() {
return true;
}
//方法返回账号是否没有被锁定 是 true
@Override
public boolean isAccountNonLocked() {
return true;
}
//方法返回账号是否没有过期 是
@Override
public boolean isCredentialsNonExpired() {
return true;
}
//方法返回账号 是否启用
@Override
public boolean isEnabled() {
return true;
}
}
5. 创建并实现 UserDao 接口(auth)
User findByAccount(String account);
package com.zhiyou100.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zhiyou100.entity.User;
/**
* @author zhangfan
* @date 2019/10/21
*/
public interface UserDao extends BaseMapper<User> {
/**
* 根据账号查询用户信息(包括用户的角色信息)
* @param account 账号
* @return 用户信息
*/
User findByAccount(String account);
}
mapper例子
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
>
>
> >
> >
> >
>
> >
> >
>
>
>
>
6. 创建 UserService 接口继承自 UserDetailsService
实现 loadUserByUsername() 方法
package com.zhiyou100.service;
import org.springframework.security.core.userdetails.UserDetailsService;
/**
* @author zhangfan
* @date 2019/10/21
*/
public interface UserService extends UserDetailsService {
}
package com.zhiyou100.service.impl;
import com.zhiyou100.dao.UserAuthDao;
import com.zhiyou100.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
/**
* @author zhangfan
* @date 2019/10/21
*/
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserAuthDao userDao;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
return userDao.findByAccount(s);
}
}
7. 编辑 MyWebSecurityConfiguration
1. 注入 UserService
2. 在
configure(AuthenticationManagerBuilder auth)
方法中使用 UserService
package com.zhiyou100.config;
import com.zhiyou100.service.UserService;
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.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @author zhangfan
* @date 2019/10/18
*/
@Configuration
@EnableWebSecurity //启用配置
public class MyWebSecurityConfigruation extends WebSecurityConfigurerAdapter {
@Autowired
private UserService userService;
/**
* 验证表单参数是否正确,账号密码是否一致
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder(){
//告诉框架使用哪种加密格式验证密码
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
//返回对象,在MyAuthorizationServerConfiguration,中使用
@Bean //把方法返回值加入spring(IOC)容器,在其他类中可以使用@Autowired注入
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
//封装了授权的验证条件,对授权请求进行验证
return super.authenticationManagerBean();
}
}
使用 jwt 作为 token:
编辑 MyAuthorizationServerConfiguration 配置文件
1. 添加
public JwtAccessTokenConverter accessTokenConverter()
和
public TokenStore tokenStore()
方法
都需要增加 @Bean 注解
2. 在
configure(AuthorizationServerEndpointsConfigurer endpoints)
方法配置 jwt
package com.zhiyou100.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.crypto.bcrypt.BCryptPasswordEncoder;
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.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
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 java.util.ArrayList;
/**用户发送授权请求给该类
* 调用MyWebSecurityConfigruation进行验证授权
* @author zhangfan
* @date 2019/10/18
*/
@Configuration
@EnableAuthorizationServer //配置当前服务为认证中心
public class MyAuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private MyJwtTokenEnhancer myJwtTokenEnhancer;
@Override //允许用post方式发送请求申请(token)授权
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
//允许表单认证
security.allowFormAuthenticationForClients();
}
@Override //设置哪些应用可以通过哪些方式申请权限(注册申请权限的应用信息)
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//注册应用
clients.inMemory()
//只颁发给信任的程序(知道正确的client和secret)
.withClient("server") //某些应用
//注册应用(clientId=server,secret=server secret需要使用bcrypt加密方式)
.secret("{bcrypt}" + new BCryptPasswordEncoder().encode("server")) //什么方式
.authorizedGrantTypes("password","refresh_token")
.scopes("all");
//.and().withClient()用于添加第二个应用
}
@Override //设置oauth2流程中使用authenticationManager进行授权验证
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
ArrayList<TokenEnhancer> tokenEnhancers = new ArrayList<>();
tokenEnhancers.add(myJwtTokenEnhancer);
tokenEnhancers.add(accessTokenConverter());
tokenEnhancerChain.setTokenEnhancers(tokenEnhancers);
//通过authenticationManager,验证表单请求参数是否正确,决定是否颁发token
endpoints.authenticationManager(authenticationManager)
.tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter())
.tokenEnhancer(tokenEnhancerChain);
}
@Bean
public JwtAccessTokenConverter accessTokenConverter(){
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
//设置jwt的加密密钥为:123456
converter.setSigningKey("123456");
return converter;
}
@Bean
public TokenStore tokenStore(){
return new JwtTokenStore(accessTokenConverter());
}
}
在 jwt 添加自定义内容:
package com.zhiyou100.config;
import com.zhiyou100.entity.User;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import java.util.HashMap;
import java.util.Map;
/**在jwt中添加自定义内容
* token增强器
* @author zhangfan
* @date 2019/10/21
*/
@Configuration
public class MyJwtTokenEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
//1.自定义内容以key-value的形式添加到map中
Map<String,Object> payload = new HashMap<>();
//获取user对象
User user = (User) oAuth2Authentication.getPrincipal();
//取出user对象属性值填入map
payload.put("userId",user.getId());
//2.添加map到jwt中
((DefaultOAuth2AccessToken)oAuth2AccessToken).setAdditionalInformation(payload);
//3.返回修改过的jwt
return oAuth2AccessToken;
}
}
在 MyAuthorizationServerConfiguration 使用 TokenEnhancer
1. 注入 MyJwtTokenEnhancer
2. 在
configure(AuthorizationServerEndpointsConfigurer endpoints)
方法配置 tokenEnhancer
在 food 中从 jwt 中读取自定义内容:
在 FoodController 创建 loginUserId 方法处理 /login-user-id 请求
从 token 中读取 userId 并返回
<!--对jwt进行解析-->
io.jsonwebtoken
jjwt
0.9.1
@Autowired
private HttpServletRequest request;
读取并解析 jwt
1. 使用 request 从 header 中读取 token
2. 从 token 中获取 jwt
3. 解析 jwt 获取 payload
4. 从 payload 中获取自定义内容
@GetMapping("/login-user-id")
public Integer loginUserId(){
//从token中读取userId并返回
// 使用request从header中读取token
//获取的token=bearer jwt
String token = request.getHeader("Authorization");
//从token中取出jwt
String jwt = token.substring(7);
//解析jwt
Claims body = Jwts.parser().setSigningKey("123456".getBytes(StandardCharsets.UTF_8)).parseClaimsJws(jwt).getBody();
//根据key和value类型 获取自定义的值
Integer userId = body.get("userId", Integer.class);
return userId;
}