SpringCloud整合SpringSecurity - JWT进行认证 ,鉴权
一. 创建认证微服务AuthenticationService
1.1 pom.xml
点击查看代码
<dependencies>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--mybatis-dynamic-sql , 可以使用mybatis plus或者自己写sql-->
<dependency>
<groupId>org.mybatis.dynamic-sql</groupId>
<artifactId>mybatis-dynamic-sql</artifactId>
<version>1.2.1</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- jwt -->
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
</dependency>
<!-- ssm spring boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- spring cloud -->
<!-- spring cloud alibba -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
1.2 创建SimpleUserDetailsService 实现 UserDetailsService接口
作用:将数据查到的用户信息和权限放进UserDetails对象,用于SpringSecurity进行认证
@Component
@Slf4j
public class SimpleUserDetailsService implements UserDetailsService {
@Autowired
private UserService userService;
private final PasswordEncoder passwordEncoder;
public SimpleUserDetailsService(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//从数据库获取用户信息
UserEntity userEntity = userService.getByUsername(s);
//从数据库获取用户的角色权限
List<UserRoles> userRolesList = userService.getRolesByUsername(s);
StringBuilder authorityBuilder = new StringBuilder();
//将角色信息放进StringBuilder中
userRolesList.forEach(r-> {
authorityBuilder.append("ROLE_").append(r.getRoleName().toUpperCase()).append(",");
//从数据库获取用户资源权限
List<RolePermissions> permissionsList = userService.getPermissionsByRole(r.getRoleName());
permissionsList.forEach(p->authorityBuilder.append(p.getPermission()).append(","));
});
//获取用户密码
String password = userEntity.getPassword();
log.info("password->"+password);
log.info("authorities->"+authorityBuilder.toString());
//返回UserDetails对象
return new User(s,passwordEncoder.encode(password), AuthorityUtils.commaSeparatedStringToAuthorityList(authorityBuilder.toString()));
}
}
1.3 创建JWTAuthenticationSuccessHandler 实现 AuthenticationSuccessHandler接口
作用:自定义登录成功请求返回的结果,SpringSecurity默认登录成功后跳转到登录前url或者"/",前后端分离项目需要登录成功后返回jwt-token
@Component
public class JWTAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
//json
private final ObjectMapper objectMapper = new ObjectMapper();
//操作redis
private final HashOperations<String, String, String> operations;
// 构造注入
public JWTAuthenticationSuccessHandler(RedisTemplate<String, String> redisTemplate) {
this.operations = redisTemplate.opsForHash();
}
@Override
public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp,
Authentication authentication)
throws IOException, ServletException {
// authentication 对象携带了当前登陆用户名等相关信息
User user = (User) authentication.getPrincipal();
resp.setContentType("application/json;charset=UTF-8");
try {
StringBuffer buffer = new StringBuffer();
user.getAuthorities().forEach(item -> {
buffer.append(item.getAuthority());
buffer.append(",");
});
buffer.deleteCharAt(buffer.length()-1);
// 用户的 username 和他所具有的权限存入 redis 中。
operations.put(JWTUtil.REDIS_HASH_KEY, user.getUsername(), buffer.toString());
// 在 jwt-token-string 的荷载(payload)中存上当前用户的名字.
String jwtStr = JWTUtil.createJWT(user.getUsername());
Map<String, String> map = new HashMap<>();
map.put("code", "10000");
map.put("msg", "success");
map.put("jwt-token", jwtStr);
PrintWriter out = resp.getWriter();
out.write(objectMapper.writeValueAsString(map));
out.flush();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
1.4 配置SpringSecurity,将JWTAuthenticationSuccessHandler的返回结果替换默认返回结果
@EnableWebSecurity(debug = false)
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private SimpleUserDetailsService userDetailsService;
@Resource
private JWTAuthenticationSuccessHandler successHandler;
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance(); // 这是一个空的、假的密码加密器。在加密时啥事没干。
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//任何请求都需要认证
http.authorizeRequests().anyRequest().authenticated();
//登录框登录,登录成功后使用自定义的JWTAuthenticationSuccessHandler
http.formLogin().successHandler(successHandler);
//禁用跨域过滤器
http.csrf().disable();
//禁用session过滤器
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
}
1.5 将服务注册进nacos
- 启动类上添加@EnableDiscoveryClient注解
@SpringBootApplication @EnableDiscoveryClient public class AuthenticationServiceApplication { public static void main(String[] args) { SpringApplication.run(AuthenticationServiceApplication.class, args); } }
- 配置bootstrap.yml
点击查看代码
spring: cloud: nacos: discovery: server-addr: 192.172.0.24:8848 password: nacos username: nacos group: Dracarys config: contextPath: /nacos server-addr: ${spring.cloud.nacos.discovery.server-addr} username: ${spring.cloud.nacos.discovery.username} password: ${spring.cloud.nacos.discovery.password} group: ${spring.cloud.nacos.discovery.group}
- 配置application.yml
点击查看代码
server: port: 8080 spring: application: name: authentication-service datasource: driver-class-name: com.mysql.cj.jdbc.Driver password: 123root456 url: jdbc:mysql://114.55.6.86:3306/security_db?serverTimezone=UTC username: root redis: host: 114.55.6.86 port: 6379 password: 123
二. 创建普通需要鉴权的微服务SecurityService
2.1 pom.xml
点击查看代码
<dependencies>
<!--jwt-->
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>9.11.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2.2 自定义JwtFilter过滤器,拦截请求,添加UseranmePasswordAuthenticationToken
@Slf4j
@Component
public class JwtFilter extends OncePerRequestFilter {
//操作redis
private final HashOperations<String, String, String> operations;
//构造注入
public JwtFilter(RedisTemplate<String, String> redisTemplate) {
this.operations = redisTemplate.opsForHash();
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
//如果security_context_holder中有authentication
if(authentication != null){
log.info("security_context_holder中有authentication");
filterChain.doFilter(request,response);
return;
}
// String jwtStr = request.getHeader("x-jwt-token");
String username = request.getHeader("x-username");
//如果请求头里没有token
if(StringUtils.isEmpty(username)){
log.info("没有username");
filterChain.doFilter(request,response);
return;
}
//jwtStr验证不通过
/*if(!JwtUtils.verify(jwtStr)){
log.info("jwtStr验证不通过");
filterChain.doFilter(request,response);
return;
}
String username = JwtUtils.getUsernameFromJWT(jwtStr);*/
//根据用户名从redis中获取权限
log.info("username->"+username);
String authorities = operations.get("jwt-token",username);
log.info("authorities->"+operations.get("jwt-token",username));
//将权限存进token中
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
username, null, AuthorityUtils.commaSeparatedStringToAuthorityList(authorities));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request,response);
}
}
2.3 配置SpringSecurity,将自定义的JwtFilter过滤器添加进SpringSecurity过滤器链
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Resource
private JwtFilter jwtFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated()
.and()
//将自定义的Jwt过滤器添加到UsernamePasswordAuthenticationFilter后面
.formLogin().and().addFilterAfter(jwtFilter, UsernamePasswordAuthenticationFilter.class);
}
}
2.4 将服务注册到Nacos
- 启动类上添加@EnableDiscoveryClient注解
@SpringBootApplication @EnableDiscoveryClient public class SecurityServiceDemo1Application { public static void main(String[] args) { SpringApplication.run(SecurityServiceDemo1Application.class, args); } }
- application.yml
点击查看代码
#日志 logging: level: root: INFO com.wn: DEBUG pattern: console: "${CONSOLE_LOG_PATTERN:%clr(${LOG_LEVEL_PATTERN:%5p}) %clr(|){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}}" server: port: 9000 spring: application: name: security-service-demo1 cloud: nacos: discovery: group: Dracarys namespace: public password: nacos server-addr: 192.172.0.24:8848 username: nacos redis: port: 6379 host: 114.55.6.86
三. 创建Gateway微服务
3.1 pom.xml
点击查看代码
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
3.2 自定义全局过滤器GatewayFilter,将jwt-token解析,返回用户名到请求头
@Component
@Slf4j
public class GatewayFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
HttpHeaders headers = exchange.getRequest().getHeaders();
List<String> strings = headers.get("x-jwt-token");
ServerHttpRequest mutateRequest = exchange.getRequest();
//校验token,成功拿到token中的username
if(strings.size()>0 && JWTUtil.verify(strings.get(0))){
String username = JWTUtil.getUsernameFromJWT(strings.get(0));
//将username存进请求头
mutateRequest = exchange.getRequest().mutate().header("x-username", username).build();
log.info("将用户名存进请求头"+username);
}
return chain.filter(exchange.mutate().request(mutateRequest).build());
}
}
3.2 将服务注册到nacos
- 启动类上添加@EnableDiscoveryClient注解
- application.yml
点击查看代码
#日志 logging: level: root: INFO com.wn: DEBUG pattern: console: "${CONSOLE_LOG_PATTERN:%clr(${LOG_LEVEL_PATTERN:%5p}) %clr(|){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}}" server: port: 88 spring: application: name: gateway cloud: nacos: discovery: server-addr: 192.172.0.24:8848 username: nacos password: nacos group: Dracarys #整合gateway和openFeign gateway: discovery: locator: enabled: true lower-case-service-id: true
四. JwtUtils
点击查看代码
package com.wn.service.util;
import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.MACVerifier;
import com.nimbusds.jose.shaded.json.JSONObject;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.util.StringUtils;
import java.util.Collection;
import java.util.Map;
@Slf4j
public class JwtUtils {
private static final String usernameKey = "username";
private static final String authoritiesKey = "authorities";
public static final String secret = "hello world goodbye thank you very much see you next time";
static {
log.info("spring security jwt secret: {}", secret);
}
@SneakyThrows
public static String createJWT(String username) {
// jwt 头
JWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.HS256).type(JOSEObjectType.JWT).build();
// jwt 荷载
JSONObject obj = new JSONObject();
obj.put(usernameKey, username);
Payload payload = new Payload(obj);
// jwt 头 + 荷载 + 密钥 = 签名
JWSSigner jwsSigner = new MACSigner(secret);
JWSObject jwsObject = new JWSObject(jwsHeader, payload);
// 进行签名(根据前两部分生成第三部分)
jwsObject.sign(jwsSigner);
// 获得 jwt string
return jwsObject.serialize();
}
@SneakyThrows
public static String createJWT(String username, Collection<? extends GrantedAuthority> authorities) {
// jwt 头
JWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.HS256).type(JOSEObjectType.JWT).build();
// jwt 荷载
JSONObject obj = new JSONObject();
obj.put(usernameKey, username);
obj.put(authoritiesKey, StringUtils.collectionToCommaDelimitedString(authorities)); // "xxx,yyy,zzz,..."
Payload payload = new Payload(obj);
// jwt 头 + 荷载 + 密钥 = 签名
JWSSigner jwsSigner = new MACSigner(secret);
JWSObject jwsObject = new JWSObject(jwsHeader, payload);
// 进行签名(根据前两部分生成第三部分)
jwsObject.sign(jwsSigner);
// 获得 jwt string
return jwsObject.serialize();
}
@SneakyThrows
public static boolean verify(String jwtString) {
JWSObject jwsObject = JWSObject.parse(jwtString);
JWSVerifier jwsVerifier = new MACVerifier(secret);
return jwsObject.verify(jwsVerifier);
}
@SneakyThrows
public static String getUsernameFromJWT(String jwtString) {
JWSObject jwsObject = JWSObject.parse(jwtString);
Map<String, Object> map = jwsObject.getPayload().toJSONObject();
return (String) map.get(usernameKey);
}
@SneakyThrows
public static String getAuthoritiesFromJwt(String jwtString) {
JWSObject jwsObject = JWSObject.parse(jwtString);
Map<String, Object> map = jwsObject.getPayload().toJSONObject();
return (String) map.get(authoritiesKey);
}
}