diff --git a/pom.xml b/pom.xml index f0630e5..b16971f 100644 --- a/pom.xml +++ b/pom.xml @@ -9,18 +9,54 @@ cn.teammodel - deployment-test - 0.0.1-SNAPSHOT - deployment-test - deployment-test + Teammodel-extension + 1.0.1 + Teammodel-extension + Teammodel-extension 1.8 + 4.11.0 + org.springframework.boot spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-security + + + + + org.springframework.boot + spring-boot-starter-oauth2-resource-server + + + + + com.azure.spring + spring-cloud-azure-starter + + + + com.azure.spring + spring-cloud-azure-starter-data-cosmos + + + + org.projectlombok + lombok + true + + + + io.jsonwebtoken + jjwt + 0.9.1 + org.springframework.boot @@ -29,11 +65,31 @@ + + + + com.azure.spring + spring-cloud-azure-dependencies + ${spring-cloud-azure.version} + pom + import + + + + org.springframework.boot spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + diff --git a/src/main/java/cn/teammodel/controller/HelloController.java b/src/main/java/cn/teammodel/controller/HelloController.java index 40c0dfe..5740065 100644 --- a/src/main/java/cn/teammodel/controller/HelloController.java +++ b/src/main/java/cn/teammodel/controller/HelloController.java @@ -1,16 +1,20 @@ package cn.teammodel.controller; import cn.teammodel.common.R; - import org.springframework.web.bind.annotation.GetMapping; - import org.springframework.web.bind.annotation.RequestMapping; - import org.springframework.web.bind.annotation.RestController; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/") public class HelloController { @GetMapping("hello") - public R helo() { - return new R(200, "sucess","hello world"); + @PreAuthorize("hasAuthority('IES')") + public R hello() { + System.out.println(SecurityContextHolder.getContext().getAuthentication()); + return new R(200, "success","hello world"); } } \ No newline at end of file diff --git a/src/main/java/cn/teammodel/security/SecurityConfiguration.java b/src/main/java/cn/teammodel/security/SecurityConfiguration.java new file mode 100644 index 0000000..32fb123 --- /dev/null +++ b/src/main/java/cn/teammodel/security/SecurityConfiguration.java @@ -0,0 +1,65 @@ +package cn.teammodel.security; + +import cn.teammodel.security.filter.AuthInnerTokenFilter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +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.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; +import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; +import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +@Configuration +@EnableWebSecurity +public class SecurityConfiguration { + @Resource + private AuthInnerTokenFilter authInnerTokenFilter; + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + // CSRF禁用,因为不使用session + .csrf().disable() + // 禁用HTTP响应标头 + .headers().cacheControl().disable() + .and() + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .authorizeRequests(authorizeRequests -> + authorizeRequests + .antMatchers("/public/**").permitAll() + .anyRequest().authenticated() + ) + .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt) // 启用 OIDC jwt filter + .addFilterAfter(authInnerTokenFilter, BearerTokenAuthenticationFilter.class); // 添加 x-auth-authToken filter + // todo: 失败处理器 + + return http.build(); + } + + /** + * OIDC: 将 jwt 中的 claim 中的 roles 放进 authorities 中 + * + * @author: winter + * @date: 2023/11/8 17:13 + */ + @Bean + public JwtAuthenticationConverter jwtAuthenticationConverter() { + JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); + grantedAuthoritiesConverter.setAuthoritiesClaimName("roles"); + grantedAuthoritiesConverter.setAuthorityPrefix(""); + + JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); + jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter); + return jwtAuthenticationConverter; + } +} diff --git a/src/main/java/cn/teammodel/security/filter/AuthInnerTokenFilter.java b/src/main/java/cn/teammodel/security/filter/AuthInnerTokenFilter.java new file mode 100644 index 0000000..b1f6eb4 --- /dev/null +++ b/src/main/java/cn/teammodel/security/filter/AuthInnerTokenFilter.java @@ -0,0 +1,23 @@ +package cn.teammodel.security.filter; + +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * x-auth-authToken filter + * @author winter + * @create 2023-11-09 10:43 + */ +@Component +public class AuthInnerTokenFilter extends OncePerRequestFilter { + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + filterChain.doFilter(request, response); + } +} diff --git a/src/main/java/cn/teammodel/security/utils/JwtTokenUtil.java b/src/main/java/cn/teammodel/security/utils/JwtTokenUtil.java new file mode 100644 index 0000000..16bb8c1 --- /dev/null +++ b/src/main/java/cn/teammodel/security/utils/JwtTokenUtil.java @@ -0,0 +1,115 @@ +package cn.teammodel.security.utils; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * @author winter + * @date 2022年11月24日 下午10:50 + * @description 生成jwt令牌的工具类 + */ +@Component +@Slf4j +public class JwtTokenUtil { + private static final String CLAIM_KEY_USERNAME = "sub"; + private static final String CLAIM_KEY_CREATED = "created"; + + @Value("${jwt.secret}") + private String secret; + @Value("${jwt.expiration}") + private Long expiration; + @Value("${jwt.tokenHead}") + private String tokenHead; + + /** + * 生成token + * @param userDetails 传入userDetails + * @return token + */ + public String generateToken(UserDetails userDetails){ + Map claims = new HashMap<>(); + claims.put(CLAIM_KEY_USERNAME,userDetails.getUsername()); + claims.put(CLAIM_KEY_CREATED,new Date()); + return generateToken(claims); + } + + private String generateToken(Map claims){ + return Jwts.builder() + .setClaims(claims) + .setExpiration(getExpirationDate()) + .signWith(SignatureAlgorithm.HS512,secret) + .compact(); + } + + /** + * 生成token过期时间 + * @return 过期时间 + */ + private Date getExpirationDate() { + return new Date(System.currentTimeMillis() + expiration * 1000); + } + + /** + * 从token中获取用户名 + * @param token 前端传入的token + * @return 负载中的用户名,获取失败返回null + */ + public String getUsernameFromToken(String token) { + String username = null; + try { + Claims claims = getClaimsFromToken(token); + username = claims.getSubject(); + }catch (Exception e){ + log.info("jwt验证出错:{}",token); + } + return username; + } + + /** + * 从token中获取Claims,异常返回null + * @param token token + * @return claims或者null + */ + private Claims getClaimsFromToken(String token){ + Claims claims = null; + try { + claims = Jwts.parser() + .setSigningKey(secret) + .parseClaimsJws(token) + .getBody(); + } catch (Exception e){ + e.printStackTrace(); + log.info("jwt解析出错:{}",token); + } + return claims; + } + + /** + * 检验token是否还有效 + * @param token 前端传入的token + * @param userDetails 登录用户细节 + * @return true表示有效,false表示无效 + */ + public boolean validateToken(String token) { + return !isTokenExpired(token); + } + + private boolean isTokenExpired(String token) { + Date expiredDate = getExpiredDateFromToken(token); + return expiredDate.before(new Date()); + } + + private Date getExpiredDateFromToken(String token) { + Claims claims = getClaimsFromToken(token); + return claims.getExpiration(); + } +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 8b13789..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..93c87b7 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,14 @@ +spring: + cloud: + azure: + cosmos: + endpoint: https://cdhabookdep-free.documents.azure.cn:443 + database: TEAMModelOS + key: v07dMthUjNk8AbwI8698AXHPbXBaaADgtgdYf4sFGXohei6aD2doq6XV45sLMGpDtDDpENAnlGkEUBu9RaAhpg== + + security: + oauth2: + resourceserver: + jwt: + issuer-uri: https://login.partner.microsoftonline.cn/4807e9cf-87b8-4174-aa5b-e76497d7392b/v2.0 + audiences: 72643704-b2e7-4b26-b881-bd5865e7a7a5 \ No newline at end of file