2 Commits

Author SHA1 Message Date
6760ee8db5 整合Shiro 2023-07-11 00:26:16 +08:00
60b061b7c6 整合Shiro 2023-07-09 23:28:42 +08:00
18 changed files with 238 additions and 350 deletions

52
pom.xml
View File

@@ -15,6 +15,7 @@
<description>netstate-proc</description>
<properties>
<java.version>17</java.version>
<org.mapstruct.version>1.5.5.Final</org.mapstruct.version>
</properties>
<dependencies>
<dependency>
@@ -25,10 +26,6 @@
<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>com.mysql</groupId>
@@ -45,26 +42,25 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.11.0</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.5.3.Final</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<version>${org.mapstruct.version}</version>
</dependency>
</dependencies>
<build>
@@ -81,6 +77,22 @@
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>

View File

@@ -0,0 +1,47 @@
package com.wuyiqi.netstateproc.config.shiro;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.JWTVerifier;
import com.auth0.jwt.interfaces.Payload;
import com.wuyiqi.netstateproc.convert.JwtConvert;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.mapstruct.factory.Mappers;
import java.time.Instant;
import java.util.List;
@Data
public class JwtToken implements AuthenticationToken {
private String token;
private String secret;
private static final JwtConvert jwtConvert = Mappers.getMapper(JwtConvert.class);
@Override
public String getPrincipal() {
return JWT.decode(token).getSubject();
}
@Override
public Payload getCredentials() throws JWTVerificationException {
Algorithm algorithm = Algorithm.HMAC256(Sha256Hash.toString(secret.getBytes()));
JWTVerifier verifier = JWT.require(algorithm).build();
return verifier.verify(token);
}
public static String generate(String subject, Long expire, List<Integer> rules, String secret) {
Algorithm algorithm = Algorithm.HMAC256(Sha256Hash.toString(secret.getBytes()));
Claims claims = new Claims(subject, Instant.now().plusSeconds(expire), Instant.now(), rules);
return JWT.create().withPayload(jwtConvert.conv2Map(claims)).sign(algorithm);
}
public record Claims(String subject, Instant expiresAt, Instant issuedAt, List<Integer> roles) {}
}

View File

@@ -0,0 +1,22 @@
package com.wuyiqi.netstateproc.config.shiro;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.subject.PrincipalCollection;
public class NetStateRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
return null;
}
}

View File

@@ -0,0 +1,22 @@
package com.wuyiqi.netstateproc.config.shiro;
import org.apache.catalina.Realm;
import org.apache.shiro.spring.config.ShiroAnnotationProcessorConfiguration;
import org.apache.shiro.spring.config.ShiroBeanConfiguration;
import org.apache.shiro.spring.config.ShiroConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import({ShiroBeanConfiguration.class,
ShiroConfiguration.class,
ShiroAnnotationProcessorConfiguration.class})
public class ShiroConfig {
@Bean
public Realm realm() {
return null;
}
}

View File

@@ -0,0 +1,28 @@
package com.wuyiqi.netstateproc.controller;
import com.wuyiqi.netstateproc.model.eneity.User;
import com.wuyiqi.netstateproc.model.rest.req.LoginReq;
import com.wuyiqi.netstateproc.model.rest.resp.Resp;
import com.wuyiqi.netstateproc.service.UserService;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
@AllArgsConstructor
public class UserController {
private final UserService userService;
@PostMapping("/login")
public Resp<String> login(@RequestBody LoginReq req) {
return null;
}
@GetMapping("/test")
public Resp<User> test() {
return Resp.success(userService.findByUsername("admin"));
}
}

View File

@@ -0,0 +1,11 @@
package com.wuyiqi.netstateproc.convert;
import com.wuyiqi.netstateproc.config.shiro.JwtToken;
import org.mapstruct.Mapper;
import java.util.Map;
@Mapper
public interface JwtConvert {
Map<String, Object> conv2Map(JwtToken.Claims claims);
}

View File

@@ -1,56 +0,0 @@
package com.wuyiqi.netstateproc.filter;
import com.wuyiqi.netstateproc.model.vo.UserInfoVo.UserBasicInfoVo;
import com.wuyiqi.netstateproc.security.token.JwtUser;
import com.wuyiqi.netstateproc.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String token = request.getHeader("Authorization");
if(!StringUtils.hasText(token) || !token.startsWith("Bearer")) {
filterChain.doFilter(request, response);
return;
}
token = token.substring(7);
String userid;
try{
Claims claims = JwtUtil.parseJWT(token);
userid = claims.getSubject();
} catch (Exception e) {
throw new RuntimeException(e);
}
UserBasicInfoVo user = new UserBasicInfoVo();
if (user == null) {
throw new RuntimeException("用户未登录");
}
JwtUser loginUser = new JwtUser(user);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, null);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request, response);
}
}

View File

@@ -0,0 +1,18 @@
package com.wuyiqi.netstateproc.model.eneity;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Data;
@Data
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String password;
private String username;
}

View File

@@ -0,0 +1,9 @@
package com.wuyiqi.netstateproc.model.rest.req;
import lombok.Data;
@Data
public class LoginReq {
private String username;
private String password;
}

View File

@@ -0,0 +1,20 @@
package com.wuyiqi.netstateproc.model.rest.resp;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.experimental.Accessors;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
@Data
@Accessors(chain = true)
@AllArgsConstructor
public class Resp<T> {
private Integer code;
private String message;
private T data;
public static <T> Resp<T> success(T data) {
return new Resp<>(HttpStatus.OK.value(), "success", data);
}
}

View File

@@ -1,29 +0,0 @@
package com.wuyiqi.netstateproc.model.vo;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
public class UserInfoVo {
@Getter
@Setter
@ToString
public static class UserBasicInfoVo {
private Long id;
private String username;
private String email;
@JsonIgnore
private String password;
private Boolean isAdmin;
private Boolean isEnabled;
}
}

View File

@@ -0,0 +1,10 @@
package com.wuyiqi.netstateproc.respository;
import com.wuyiqi.netstateproc.model.eneity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
//@Repository
public interface UserRepo extends JpaRepository<User, Long> {
User findByUsername(String username);
}

View File

@@ -1,90 +0,0 @@
package com.wuyiqi.netstateproc.security.config;
import com.wuyiqi.netstateproc.filter.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
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.WebSecurityCustomizer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Bean
public JwtAuthenticationTokenFilter authFilter() throws Exception {
return new JwtAuthenticationTokenFilter();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// 前后端分离架构不需要csrf保护
.authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests
// 允许所有OPTIONS请求
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
// 允许直接访问授权登录接口
//一般浏览器访问url为get请求而前端请求设置中为post增加安全
.requestMatchers(HttpMethod.POST, "/user/account/token/").permitAll()
// 允许 SpringMVC 的默认错误地址匿名访问
.requestMatchers("/error").permitAll()
// 其他所有接口必须有Authority信息Authority在登录成功后的UserDetailsImpl对象中默认设置“ROLE_USER”
//.requestMatchers("/**").hasAnyAuthority("ROLE_USER")
// 所有请求将会拦截,除了已经登录
.anyRequest().authenticated()
);
//这里默认为security登录页面在后续需要关闭时进行
// 添加 JWT 过滤器JWT 过滤器在用户名密码认证过滤器之前
http.addFilterBefore(authFilter(), UsernamePasswordAuthenticationFilter.class);
//使配置生效
return http.build();
}
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
// 将前端请求全双工放行
//使用WebSecurity.ignoring()忽略某些URL请求这些请求将被Spring Security忽略这意味着这些URL将有受到 CSRF、XSS、Clickjacking 等攻击的可能。
return (web) -> web.ignoring().requestMatchers("/websocket/**");
}
/**
* 配置跨源访问(CORS)
*
* @return
*/
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOriginPattern("*");
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.setAllowCredentials(true);
source.registerCorsConfiguration("/**", corsConfiguration);
return new CorsFilter(source);
}
}

View File

@@ -1,20 +0,0 @@
package com.wuyiqi.netstateproc.security.service;
import com.wuyiqi.netstateproc.security.token.JwtUser;
import com.wuyiqi.netstateproc.service.UserInfoService;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@RequiredArgsConstructor
@Service("userDetailService")
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserInfoService userInfoService;
@Override
public JwtUser loadUserByUsername(String username) throws UsernameNotFoundException {
return null;
}
}

View File

@@ -1,53 +0,0 @@
package com.wuyiqi.netstateproc.security.token;
import com.wuyiqi.netstateproc.model.vo.UserInfoVo.UserBasicInfoVo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
@Data
@RequiredArgsConstructor
@AllArgsConstructor
public class JwtUser implements UserDetails {
private UserBasicInfoVo userBasicInfoVo;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public String getPassword() {
return userBasicInfoVo.getPassword();
}
@Override
public String getUsername() {
return userBasicInfoVo.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return false;
}
@Override
public boolean isAccountNonLocked() {
return false;
}
@Override
public boolean isCredentialsNonExpired() {
return false;
}
@Override
public boolean isEnabled() {
return userBasicInfoVo.getIsEnabled();
}
}

View File

@@ -1,19 +0,0 @@
package com.wuyiqi.netstateproc.service;
import org.springframework.stereotype.Service;
@Service
public interface UserInfoService {
void insertUser();
void deleteById(Long userId);
void restoreById(Long userId);
void updateEmail();
void updatePassword();
void updateForgetPassword();
}

View File

@@ -0,0 +1,19 @@
package com.wuyiqi.netstateproc.service;
import com.wuyiqi.netstateproc.model.eneity.User;
import com.wuyiqi.netstateproc.respository.UserRepo;
import lombok.AllArgsConstructor;
import org.springframework.data.domain.Example;
import org.springframework.stereotype.Service;
@Service
@AllArgsConstructor
public class UserService {
private final UserRepo userRepo;
public User findByUsername(String username) {
return userRepo.findByUsername(username);
}
}

View File

@@ -1,63 +0,0 @@
package com.wuyiqi.netstateproc.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;
@Component
public class JwtUtil {
public static final long JWT_TTL = 60 * 60 * 1000L * 24 * 2;
public static final String JWT_KEY = "ChengXuanWangZhaoLongWUYIQI666";
public static String getUUID() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
public static String createJWT(String subject) {
JwtBuilder builder = getJwtBuilder(subject, null, getUUID());
return builder.compact();
}
private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
SecretKey secretKey = generalKey();
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
if (ttlMillis == null) {
ttlMillis = JwtUtil.JWT_TTL;
}
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
return Jwts.builder()
.setId(uuid)
.setSubject(subject)
.setIssuer("sg")
.setIssuedAt(now)
.signWith(secretKey, signatureAlgorithm)
.setExpiration(expDate);
}
public static SecretKey generalKey() {
byte[] encodeKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
return new SecretKeySpec(encodeKey, 0, encodeKey.length, "HmacSHA256");
}
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJwt(jwt)
.getBody();
}
}