增加 jwt 认证接口
diff --git a/config/application-devel-pg-local.properties b/config/application-devel-pg-local.properties
index 39d4caf..3602345 100644
--- a/config/application-devel-pg-local.properties
+++ b/config/application-devel-pg-local.properties
@@ -16,4 +16,3 @@
jwt.secret=Zj5taLomEbrM0lk+NMQZbHfSxaDU1wekjT+kiC3YzDw=
# timeout seconds
jwt.expiration=3600
-jwt.header=payapi
diff --git a/sql/init_test.sql b/sql/init_test.sql
index d4396a6..c13e4cf 100644
--- a/sql/init_test.sql
+++ b/sql/init_test.sql
@@ -1,4 +1,4 @@
-insert into tt_apiclient(appid, secret, status)
-values ('100001', 'oUw2NmA09ficiVWD4TUQLDOkPyzQa3VzbjjsW0B2qTk=', 'normal');
+insert into tb_apiclient(appid, secret, status, roles)
+values ('100001', 'oUw2NmA09ficiVWD4TUQLDOkPyzQa3VzbjjsW0B2qTk=', 'normal', 'ROLE_THIRD_ADMIN');
commit;
\ No newline at end of file
diff --git a/src/main/java/com/supwisdom/dlpay/framework/core/JwtConfig.java b/src/main/java/com/supwisdom/dlpay/framework/core/JwtConfig.java
new file mode 100644
index 0000000..1eccb8a
--- /dev/null
+++ b/src/main/java/com/supwisdom/dlpay/framework/core/JwtConfig.java
@@ -0,0 +1,26 @@
+package com.supwisdom.dlpay.framework.core;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class JwtConfig {
+ @Value("${jwt.secret}")
+ private String secret;
+ @Value("${jwt.expiration:3600}")
+ private Long expiration = 3600L;
+ @Value("${jwt.header:Authorization}")
+ private String header = "Authorization";
+
+ public String getSecret() {
+ return secret;
+ }
+
+ public Long getExpiration() {
+ return expiration;
+ }
+
+ public String getHeader() {
+ return header;
+ }
+}
diff --git a/src/main/java/com/supwisdom/dlpay/framework/core/JwtTokenUtil.java b/src/main/java/com/supwisdom/dlpay/framework/core/JwtTokenUtil.java
index 86060cf..20344ab 100644
--- a/src/main/java/com/supwisdom/dlpay/framework/core/JwtTokenUtil.java
+++ b/src/main/java/com/supwisdom/dlpay/framework/core/JwtTokenUtil.java
@@ -1,29 +1,30 @@
package com.supwisdom.dlpay.framework.core;
+import org.jose4j.jwa.AlgorithmConstraints;
import org.jose4j.jwk.JsonWebKey;
import org.jose4j.jws.AlgorithmIdentifiers;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.jwt.JwtClaims;
+import org.jose4j.jwt.consumer.InvalidJwtException;
+import org.jose4j.jwt.consumer.JwtConsumer;
+import org.jose4j.jwt.consumer.JwtConsumerBuilder;
import org.jose4j.lang.JoseException;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
-import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.stereotype.Component;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
-@Configuration
-@EnableAutoConfiguration
-@Component
public class JwtTokenUtil {
- @Value("${jwt.secret}")
- private String secret;
- @Value("${jwt.expiration}")
- private Long expiration = 3600L;
- @Value("${jwt.header}")
- private String header = "supwisdom";
+ private JwtConfig jwtConfig;
+
+ public JwtTokenUtil(JwtConfig config) {
+ this.jwtConfig = config;
+ }
+
+ public String getHeader() {
+ return jwtConfig.getHeader();
+ }
public String generateToken(Map<String, Object> params) throws JoseException {
JwtClaims claims = new JwtClaims();
@@ -31,13 +32,16 @@
if (params.get("audience") != null) {
claims.setAudience(params.get("audience").toString());
}
- claims.setExpirationTimeMinutesInTheFuture(expiration / 60); // time when the token will expire (10 minutes from now)
+ claims.setExpirationTimeMinutesInTheFuture(jwtConfig.getExpiration() / 60); // time when the token will expire (10 minutes from now)
claims.setGeneratedJwtId();
claims.setIssuedAtToNow(); // when the token was issued/created (now)
claims.setNotBeforeMinutesInThePast(2); // time before which the token is not yet valid (2 minutes ago)
if (params.get("subject") != null) {
claims.setSubject(params.get("subject").toString()); // the subject/principal is whom the token is about
}
+ if (params.get("authorities") != null) {
+ claims.setClaim("authorities", params.get("authorities"));
+ }
/*
claims.setClaim("email", "mail@example.com"); // additional claims/attributes about the subject can be added
List<String> groups = Arrays.asList("group-one", "other-group", "group-three");
@@ -46,7 +50,7 @@
Map<String, Object> keySpec = new HashMap<>();
keySpec.put("kty", "oct");
- keySpec.put("k", secret);
+ keySpec.put("k", jwtConfig.getSecret());
JsonWebKey key = JsonWebKey.Factory.newJwk(keySpec);
JsonWebSignature jws = new JsonWebSignature();
jws.setPayload(claims.toJson());
@@ -61,4 +65,43 @@
claims.put("uid", userDetails.getUsername());
return generateToken(claims);
}
+
+ public Map<String, List<Object>> verifyToken(String token) throws JoseException, InvalidJwtException {
+ Map<String, Object> keySpec = new HashMap<>();
+ keySpec.put("kty", "oct");
+ keySpec.put("k", jwtConfig.getSecret());
+ JsonWebKey key = JsonWebKey.Factory.newJwk(keySpec);
+ JwtConsumer jwtConsumer = new JwtConsumerBuilder()
+ .setRequireExpirationTime() // the JWT must have an expiration time
+ .setAllowedClockSkewInSeconds(30) // allow some leeway in validating time based claims to account for clock skew
+ .setRequireSubject() // the JWT must have a subject claim
+ .setExpectedIssuer("supwisdom") // whom the JWT needs to have been issued by
+ .setVerificationKey(key.getKey()) // verify the signature with the public key
+ .setJwsAlgorithmConstraints( // only allow the expected signature algorithm(s) in the given context
+ new AlgorithmConstraints(org.jose4j.jwa.AlgorithmConstraints.ConstraintType.WHITELIST, // which is only RS256 here
+ AlgorithmIdentifiers.HMAC_SHA256))
+ .build(); // create the JwtConsumer instance
+
+ // Validate the JWT and process it to the Claims
+ JwtClaims jwtClaims = jwtConsumer.processToClaims(token);
+ System.out.println("JWT validation succeeded! " + jwtClaims);
+ return jwtClaims.flattenClaims();
+ // InvalidJwtException will be thrown, if the JWT failed processing or validation in anyway.
+ // Hopefully with meaningful explanations(s) about what went wrong.
+
+// // Programmatic access to (some) specific reasons for JWT invalidity is also possible
+// // should you want different error handling behavior for certain conditions.
+//
+// // Whether or not the JWT has expired being one common reason for invalidity
+// if (e.hasExpired())
+// {
+// System.out.println("JWT expired at " + e.getJwtContext().getJwtClaims().getExpirationTime());
+// }
+//
+// // Or maybe the audience was invalid
+// if (e.hasErrorCode(ErrorCodes.AUDIENCE_INVALID))
+// {
+// System.out.println("JWT had wrong audience: " + e.getJwtContext().getJwtClaims().getAudience());
+// }
+ }
}
diff --git a/src/main/java/com/supwisdom/dlpay/framework/domain/ApiClientRedis.java b/src/main/java/com/supwisdom/dlpay/framework/domain/ApiClientRedis.java
index faee901..2cb43eb 100644
--- a/src/main/java/com/supwisdom/dlpay/framework/domain/ApiClientRedis.java
+++ b/src/main/java/com/supwisdom/dlpay/framework/domain/ApiClientRedis.java
@@ -11,6 +11,8 @@
String token;
+ String roles;
+
String loginTimestamp;
public String getId() {
@@ -36,4 +38,12 @@
public void setLoginTimestamp(String loginTimestamp) {
this.loginTimestamp = loginTimestamp;
}
+
+ public String getRoles() {
+ return roles;
+ }
+
+ public void setRoles(String roles) {
+ this.roles = roles;
+ }
}
diff --git a/src/main/java/com/supwisdom/dlpay/framework/domain/TApiClient.java b/src/main/java/com/supwisdom/dlpay/framework/domain/TApiClient.java
index 35f2610..385bff9 100644
--- a/src/main/java/com/supwisdom/dlpay/framework/domain/TApiClient.java
+++ b/src/main/java/com/supwisdom/dlpay/framework/domain/TApiClient.java
@@ -18,6 +18,9 @@
@Column(name = "status", nullable = false, length = 10)
private String status;
+ @Column(name = "roles", length = 300)
+ private String roles;
+
public String getAppid() {
return appid;
}
@@ -41,4 +44,12 @@
public void setStatus(String status) {
this.status = status;
}
+
+ public String getRoles() {
+ return roles;
+ }
+
+ public void setRoles(String roles) {
+ this.roles = roles;
+ }
}
diff --git a/src/main/kotlin/com/supwisdom/dlpay/PayApiApplication.kt b/src/main/kotlin/com/supwisdom/dlpay/PayApiApplication.kt
index b5dd0e5..7ff33c6 100644
--- a/src/main/kotlin/com/supwisdom/dlpay/PayApiApplication.kt
+++ b/src/main/kotlin/com/supwisdom/dlpay/PayApiApplication.kt
@@ -10,25 +10,12 @@
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
-import org.springframework.core.annotation.Order
import org.springframework.data.redis.connection.RedisConnectionFactory
import org.springframework.data.redis.connection.RedisPassword
import org.springframework.data.redis.connection.RedisStandaloneConfiguration
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories
-import org.springframework.security.authentication.ProviderManager
-import org.springframework.security.authentication.dao.DaoAuthenticationProvider
-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.WebSecurityConfigurerAdapter
-import org.springframework.security.config.http.SessionCreationPolicy
-import org.springframework.security.core.userdetails.User
-import org.springframework.security.core.userdetails.UserDetailsService
-import org.springframework.security.provisioning.InMemoryUserDetailsManager
-import org.springframework.security.web.authentication.AuthenticationFailureHandler
-import org.springframework.security.web.authentication.logout.CookieClearingLogoutHandler
-import javax.servlet.Filter
@Configuration
@@ -62,79 +49,6 @@
}
}
-@EnableWebSecurity
-class WebSecurityConfig {
- @Bean
- fun userDetailsService(): UserDetailsService {
- val manager = InMemoryUserDetailsManager()
- manager.createUser(User.withDefaultPasswordEncoder()
- .username("admin")
- .password("123456")
- .roles("USER").build())
- return manager
- }
-
- @Bean
- fun daoProvider(detailsService: UserDetailsService): DaoAuthenticationProvider {
- return DaoAuthenticationProvider().also {
- it.setUserDetailsService(detailsService)
- }
- }
-
- @Bean
- fun providerManager(daoProvider: DaoAuthenticationProvider): ProviderManager {
- return ProviderManager(listOf(daoProvider))
- }
-
-
- companion object {
- @Configuration
- @Order(1)
- class ApiWebSecurityConfigurationAdapter : WebSecurityConfigurerAdapter() {
-
- override fun configure(http: HttpSecurity) {
- http.authorizeRequests()
-// .antMatchers("/login", "/resources/**", "/about", "/common/**").permitAll()
-// .antMatchers("/admin/**").hasRole("ADMIN")
-// .antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
- .antMatchers("/**").permitAll()
- .antMatchers("/admin/**").hasRole("ADMIN")
- .anyRequest().authenticated()
- .and().httpBasic()
- .and()
- .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
-// .oauth2Client()
-// .clientRegistrationRepository(clientRegistrationRepository)
-// .authorizedClientRepository(this.authorizedClientRepository())
-// .authorizedClientService(this.authorizedClientService())
-// .authorizationCodeGrant()
-// .authorizationRequestRepository(this.authorizationRequestRepository())
-// .authorizationRequestResolver(this.authorizationRequestResolver())
-// .accessTokenResponseClient(this.accessTokenResponseClient())
- }
- }
-
-
- @Configuration
- class MvcWebSecurityConfigurationAdapter : WebSecurityConfigurerAdapter() {
- override fun configure(http: HttpSecurity) {
- http.authorizeRequests()
- .anyRequest().authenticated()
- .and()
- .formLogin()
- .loginPage("/login")
- .loginProcessingUrl("/login/form")
- .and()
- .logout()
- .logoutSuccessUrl("/login")
- .invalidateHttpSession(true)
- .addLogoutHandler(CookieClearingLogoutHandler())
- }
- }
- }
-}
-
-
@SpringBootApplication
class PayApiApplication
diff --git a/src/main/kotlin/com/supwisdom/dlpay/framework/controller/security_controller.kt b/src/main/kotlin/com/supwisdom/dlpay/framework/controller/security_controller.kt
index fe3c279..cde879a 100644
--- a/src/main/kotlin/com/supwisdom/dlpay/framework/controller/security_controller.kt
+++ b/src/main/kotlin/com/supwisdom/dlpay/framework/controller/security_controller.kt
@@ -1,6 +1,7 @@
package com.supwisdom.dlpay.framework.controller
import com.supwisdom.dlpay.framework.ResponseBodyBuilder
+import com.supwisdom.dlpay.framework.core.JwtConfig
import com.supwisdom.dlpay.framework.core.JwtTokenUtil
import com.supwisdom.dlpay.framework.dao.ApiClientDao
import com.supwisdom.dlpay.framework.domain.AppClientRedis
@@ -12,6 +13,7 @@
import com.supwisdom.dlpay.framework.service.SystemUtilService
import com.supwisdom.dlpay.framework.util.HmacUtil
import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.social.connect.web.HttpSessionSessionStrategy
import org.springframework.stereotype.Controller
@@ -25,7 +27,7 @@
import javax.servlet.http.HttpServletResponse
@RestController
-@RequestMapping("/auth")
+@RequestMapping("/api/auth")
class ApiAuthController {
@Autowired
@@ -38,13 +40,13 @@
lateinit var systemUtil: SystemUtilService
@Autowired
- lateinit var jwtUtil: JwtTokenUtil
+ lateinit var jwtConfig: JwtConfig
@GetMapping("/gettoken")
fun loginInit(appid: String): ResponseEntity<Any> {
apiClient.findById(appid).run {
if (!isPresent) {
- return ResponseEntity.status(401).build()
+ return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build()
}
if (get().status != "normal") {
return ResponseEntity.ok(ResponseBodyBuilder.create()
@@ -57,6 +59,7 @@
ApiClientRedis().apply {
id = appid
loginTimestamp = now
+ roles = it.roles
this.token = HmacUtil.HMACSHA256(token, it.secret)
}.also {
repo.save(it)
@@ -86,15 +89,16 @@
client.token = ""
repo.save(client)
}
- val token = jwtUtil.generateToken(
+ val token = JwtTokenUtil(jwtConfig).generateToken(
mapOf("uid" to appid, "issuer" to "supwisdom",
- "subject" to "payapi"))
+ "subject" to "payapi",
+ "authorities" to it.get().roles.split(";")))
ResponseEntity.ok(ResponseBodyBuilder.create()
.data("jwt", token)
.data("appid", appid)
.success())
} else {
- ResponseEntity.status(401).build()
+ ResponseEntity.status(HttpStatus.UNAUTHORIZED).build()
}
}
}
diff --git a/src/main/kotlin/com/supwisdom/dlpay/security.kt b/src/main/kotlin/com/supwisdom/dlpay/security.kt
new file mode 100644
index 0000000..26e2df7
--- /dev/null
+++ b/src/main/kotlin/com/supwisdom/dlpay/security.kt
@@ -0,0 +1,120 @@
+package com.supwisdom.dlpay
+
+import com.supwisdom.dlpay.framework.core.JwtConfig
+import com.supwisdom.dlpay.framework.core.JwtTokenUtil
+import org.jose4j.jwt.consumer.InvalidJwtException
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.core.annotation.Order
+import org.springframework.http.HttpStatus
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
+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.WebSecurityConfigurerAdapter
+import org.springframework.security.config.http.SessionCreationPolicy
+import org.springframework.security.core.authority.SimpleGrantedAuthority
+import org.springframework.security.core.context.SecurityContextHolder
+import org.springframework.security.core.userdetails.User
+import org.springframework.security.core.userdetails.UserDetailsService
+import org.springframework.security.provisioning.InMemoryUserDetailsManager
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
+import org.springframework.security.web.authentication.logout.CookieClearingLogoutHandler
+import org.springframework.web.filter.OncePerRequestFilter
+import javax.servlet.FilterChain
+import javax.servlet.http.HttpServletRequest
+import javax.servlet.http.HttpServletResponse
+
+
+class ApiJwtAuthenticationFilter(jwt: JwtTokenUtil) : OncePerRequestFilter() {
+ private val jwtUtil = jwt
+
+ override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain) {
+ request.getHeader(jwtUtil.header)?.let { jwt ->
+ try {
+ val claims = jwtUtil.verifyToken(jwt)
+ val auth = UsernamePasswordAuthenticationToken(claims["uid"], null,
+ claims["authorities"]?.map { SimpleGrantedAuthority(it as String) })
+ SecurityContextHolder.getContext().authentication = auth
+ } catch (e: InvalidJwtException) {
+ SecurityContextHolder.clearContext();
+ response.sendError(HttpStatus.BAD_REQUEST.value(), e.message)
+ }
+ }
+ filterChain.doFilter(request, response)
+ }
+}
+
+@EnableWebSecurity
+class WebSecurityConfig {
+
+ @Bean
+ fun userDetailsService(): UserDetailsService {
+ val manager = InMemoryUserDetailsManager()
+ manager.createUser(User.withDefaultPasswordEncoder()
+ .username("admin")
+ .password("123456")
+ .roles("USER").build())
+ return manager
+ }
+
+// @Bean
+// fun daoProvider(detailsService: UserDetailsService): DaoAuthenticationProvider {
+// return DaoAuthenticationProvider().also {
+// it.setUserDetailsService(detailsService)
+// }
+// }
+//
+// @Bean
+// fun providerManager(daoProvider: DaoAuthenticationProvider): ProviderManager {
+// return ProviderManager(listOf(daoProvider))
+// }
+
+
+ companion object {
+ @Configuration
+ @Order(1)
+ class ApiWebSecurityConfigurationAdapter : WebSecurityConfigurerAdapter() {
+
+ @Autowired
+ lateinit var jwtConfig: JwtConfig
+
+ override fun configure(http: HttpSecurity) {
+ // 设置 API 访问权限管理
+ http.csrf().disable()
+ .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
+ .and()
+ .addFilterAfter(ApiJwtAuthenticationFilter(JwtTokenUtil(jwtConfig)),
+ UsernamePasswordAuthenticationFilter::class.java)
+ .authorizeRequests()
+ .antMatchers("/api/auth/**").permitAll()
+ .antMatchers("/api/common/**").hasAnyRole("THIRD_COMMON", "THIRD_ADMIN")
+ .antMatchers("/api/consume/**").hasRole("THIRD_CONSUME")
+ .antMatchers("/api/deposit/**").hasRole("THIRD_DEPOSIT")
+ .antMatchers("/api/user/**").hasAnyRole("THIRD_COMMON", "THIRD_ADMIN")
+ .antMatchers("/api/shop/**").hasRole("THIRD_SHOP")
+ .anyRequest().authenticated()
+ // 注册 filter
+ }
+ }
+
+ @Configuration
+ class MvcWebSecurityConfigurationAdapter : WebSecurityConfigurerAdapter() {
+
+ override fun configure(http: HttpSecurity) {
+ // 设置 Web MVC 应用权限
+ http.authorizeRequests()
+ .anyRequest().authenticated()
+ .and()
+ .formLogin()
+ .loginPage("/user/login").permitAll()
+ .and()
+ .logout()
+ .logoutUrl("/user/logout")
+ .logoutSuccessUrl("/user/home")
+ .invalidateHttpSession(true)
+ .addLogoutHandler(CookieClearingLogoutHandler())
+ }
+ }
+ }
+}
diff --git a/src/main/kotlin/com/supwisdom/dlpay/user/controller/user_controller.kt b/src/main/kotlin/com/supwisdom/dlpay/user/controller/user_controller.kt
index 9714aea..d92da83 100644
--- a/src/main/kotlin/com/supwisdom/dlpay/user/controller/user_controller.kt
+++ b/src/main/kotlin/com/supwisdom/dlpay/user/controller/user_controller.kt
@@ -43,7 +43,7 @@
.success())
}
- @GetMapping("/get")
+ @PostMapping("/get")
fun queryShop(@RequestBody request: UserParam): ResponseEntity<Any> {
if (!request.uniqueId.isNullOrEmpty()) {
val person = useService.findByThirdUniqueIdenty(request.uniqueId!!)