一. 創建認證微服務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);
}
}