diff --git a/README.md b/README.md index 03e5c45e1139aa32ea50ce745806a2bfaabd5af3..559f0bc99220ee9231636312ea5e17d855db6ffd 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,13 @@ ## oauth2-shiro -整合Apache OltuShiro. 提供一个轻量的OAUTH2应用框架. +--- +整合Apache OltuShiro. 提供一个轻量的OAUTH2应用框架实现. 并根据不同的应用场景提供不同的实现(如web场景,移动设备). -该项目与spring-oauth-server实现相同的需求与场合. -只是在实现上使用的技术不同(spring-oauth-server使用Spring Security + spring-security-oauth2实现; oauth2-oltu实现); +该项目与spring-oauth-server实现相同的需求与场合. +只是在实现上使用的技术不同(spring-oauth-server使用Spring Security + spring-security-oauth2实现); 相比spring-oauth-server, oauth2-oltu具有如下特点:
@@ -19,25 +20,25 @@

-OAuth2下一代身份认证授权协议OIDC实现: MyOIDC +OAuth2下一代身份认证授权协议OIDC实现: MyOIDC

主要技术及版本

- Spring -- 3.2.2.RELEASE + Spring Boot-- 2.4.13
oltu -- 1.0.2
shiro -- 1.11.0
- MySQL -- 5.6 + MySQL -- 5.6+

开发环境


@@ -47,7 +48,7 @@ @@ -55,17 +56,18 @@

在线测试

Redis版本

+ Redis版本的实现主要是将Token数据存储换成Redis,并利用TTL特性自动清除过期数据,性能更高。
@@ -73,51 +75,57 @@

如何使用

  1. - 项目是Maven管理的, 需要本地安装maven(开发用的maven版本号为3.1.0), 还有MySql(开发用的mysql版本号为5.6) + 项目是Maven管理的, 需要本地安装maven(开发用的maven版本号为3.6.0), 数据库MySql(开发用的mysql版本号为5.7.22)
  2. - 下载(或clone)项目到本地 + 下载(或clone)项目到本地
  3. - 项目由三个模块(core,authz,resources)组成, core是一个Java项目(jar), authz与resources是Java Web项目(.war) + 项目由三个模块(core,authz,resources)组成, core是一个Java项目(jar), authz与resources是SpringBoot项目(.jar)
  4. - 创建MySQL数据库(如数据库名 oauth2_shiro), 并运行相应的SQL脚本(脚本文件位于others/database目录), + 创建MySQL数据库(数据库名 oauth2_shiro), 并运行相应的SQL脚本(脚本文件位于others/database目录),
    运行脚本的顺序: oauth2-shiro.ddl -> initial-db.ddl +
    + 若需要运行单元测试,则还需要创建数据库 oauth2_shiro_test 并运行SQL脚本 +(单元测试的配置文件为application-test.properties,位于 src/test/resources 目录中)。
  5. - 依次修改authz模块的配置文件authz.properties(位于模块的src/main/resources目录)与resources模块的配置文件resources.properties(位于模块的src/main/resources目录); + 依次修改authz模块的配置文件application.properties(位于模块的src/main/resources目录)与resources模块的配置文件application.properties(位于模块的src/main/resources目录); 修改配置文件中的数据库连接信息(包括username, password等), 都连接到数据库oauth2_shiro
  6. -将本地项目导入到IDE(如Intellij IDEA)中,配置Tomcat(或类似的servlet运行服务器), 并启动Tomcat(默认端口为8080); +将本地项目导入到IDE(如Intellij IDEA)中, 并启动服务。 +
    +(authz默认端口为8080,启动类 AuthzApplication.java; resources默认端口为8083,启动类 ResourcesApplication.java )。
    -注意将项目的 contextPath(根路径) 设置为 'os'. +启动后查看控制台的日志输出或查看logs/目录中按日期分类的log日志。
    - 另: 也可通过maven package命令将项目编译为war文件(os.war), 注意编译时每个模块的pom.xml文件中配置的数据库连接信息, 可在Maven命令中添加 -Dmaven.test.skip=true 忽略测试; - 将authz模块与resources模块生成的war放在Tomcat中并启动(注意: 这种方式需要将 authz.properties与resources.properties 加入到classpath中并正确配置数据库连接信息). + 另: 也可通过maven package命令将项目编译为SpringBoot jar文件(authz.jar 与 rs.jar), 注意编译时每个模块的pom.xml文件中配置的数据库连接信息, 可在Maven命令中添加 -Dmaven.test.skip=true 忽略单元测试; + 将authz模块与resources模块生成的jar通过 java -jar 命令启动( java -jar authz.jarjava -jar rs.jar)。
  7. - 参考oauth_test.txt(位于others目录)的内容并测试之(也可在浏览器中访问相应的地址,如: http://localhost:8080/os/). + 在浏览器中直接访问authz地址: http://localhost:8080 在线测试或 + 参考oauth_test.txt(位于others目录)的内容使用postman等工具测试。
-
-支持的 grant_type -
-说明 oauth2-shiro 项目支持的grant_type(授权方式)与功能 +

支持的 grant_type

+ +说明 oauth2-shiro 项目支持的OAuth2 grant_type (授权方式)与功能
  1. authorization_code -- 授权码模式(即先登录获取code,再获取token)
  2. -
  3. password -- 密码模式(将用户名,密码传过去,直接获取token)
  4. +
  5. password -- 密码模式(将用户名,密码传过去,直接获取token) [不推荐]
  6. refresh_token -- 刷新access_token
  7. -
  8. implicit(token) -- 简化模式(在redirect_uri 的Hash传递token; Auth客户端运行在浏览器中,如JS,Flash)
  9. +
  10. implicit(token) -- 简化模式(在redirect_uri 的Hash传递token; Auth客户端运行在浏览器中,如JS,Flash) [不推荐]
  11. client_credentials -- 客户端模式(无用户,用户向客户端注册,然后客户端以自己的名义向'服务端'获取资源)
- +在OAuth2.1中对 grant_type的使用进行了安全升级(如不推荐使用 password 方式), +详细变化请查看 OAuth2.1的状态与主要特征
@@ -125,7 +133,7 @@

从 0.2版本开始将项目的所有计划的开发内容列出来, 方便大家跟进, 也欢迎你加入.
-项目的开发管理使用开源项目 andaily-developer. +项目的开发管理使用开源项目 andaily-developer.

计划加入Spring-Boot的实现 @@ -133,13 +141,40 @@

-
-Project Log -

+ +

+

项目动态

  1. 2015-05-17 Initial project, start push code (private)

  2. 2015-07-16 oauth2-shiro项目开发状态(7月)

  3. 2015-09-06 oauth2-shiro项目开发状态(8月)

  4. 2015-09-06 项目由 私有 变为 开源, 开发 resource 模块

  5. -
  6. 2015-09-26 版本0.1 开发完毕, 发布 0.1-beta 版本

  7. -
  8. 2015-10-07 重构项目结构, 发布 0.1-rc 版本

  9. +
  10. 2015-09-26 版本0.1 开发完毕, 发布 0.1-beta 版本

  11. +
  12. 2015-10-07 重构项目结构, 发布 0.1-rc 版本

  13. 2016-05-26 开始开发 0.2 版本

  14. 2016-07-02 添加在线测试环境

  15. -
  16. 2016-08-17 发布 0.2 版本

  17. -
  18. 2017-01-21 加入到GitHub中(https://github.com/monkeyk/oauth2-shiro.git)

  19. -
  20. 2020-04-26 推荐七个非常实用的OAuth开源项目

  21. +
  22. 2016-08-17 发布 0.2 版本

  23. +
  24. 2017-01-21 加入到GitHub中, Git@OSC地址: http://git.oschina.net/mkk/oauth2-shiro

  25. +
  26. 2020-07-05 开始2.0.0版本开发

  27. +
  28. 2023-09-28 发布 2.0.0 版本,大升级

-

- - - -
- -
-

项目动态

-

姊妹项目

-
-

+

问答与讨论

+ 与OAuth2相关的技术文章请访问 https://andaily.com/blog/?cat=19 (不断更新与OAuth相关的文章) - -

- 问答与讨论 -
+
+
与项目相关的,与OAuth相关的问题与回答,以及各类讨论请访问
https://andaily.com/blog/?dwqa-question_category=oauth -

+
-

- 捐助 -
- 支付宝: monkeyking1987@126.com (**钊) -
- 明瑞 -- 5元 -
- Triton -- 8.8元 -
-    半个鼠标 -- 10元 -
- 张宏俊 -- 20元 (2018-03-28) -

+ +

捐赠历史

+ +支付宝: monkeyking1987@126.com (**钊) +
+
+

开源扩展

关注更多我的开源项目请访问 https://andaily.com/my_projects.html

-

- 若需更多的支持请联系 sz@monkeyk.com -

-

- WeChat -

+ + 若需更多的商业技术支持请联系 monkeyk1987@gmail.com或发私信。 + + diff --git a/authz/pom.xml b/authz/pom.xml index af0777011b9f57536dfa47fbc7e6ca1be894e1ab..ec4ade0bda349bee1fef85af62725115c31d5b28 100644 --- a/authz/pom.xml +++ b/authz/pom.xml @@ -4,27 +4,36 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.13 + + + com.monkeyk authz - 0.3 + 2.0.0 ${project.artifactId} - war + jar oltu and shiro [AUTHZ] + 1.8 UTF-8 - 3.2.2.RELEASE - 1.6.12 + + + ${project.version} - 5.1.35 + 1.0.2 1.11.0 - 2.9.10.8 + - com.mysql.jdbc.Driver + com.mysql.cj.jdbc.Driver jdbc:mysql://localhost:3306/oauth2_shiro_test?autoReconnect=true&useUnicode=true&characterEncoding=utf8 andaily andaily @@ -34,192 +43,15 @@ - - authz - - - - maven-compiler-plugin - 3.2 - - 1.7 - 1.7 - UTF-8 - - - - - maven-war-plugin - 2.6 - - */classes/authz.properties - */classes/authz.properties - - false - - ${project.version} - monkeyk.com - monkeyk.com - ${project.name} - ${project.version} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - maven-surefire-plugin - 2.4 - - ${test.skip} - none - - **/*Test.java - - - - jdbc.url - ${jdbc.url} - - - jdbc.username - ${jdbc.user} - - - jdbc.password - ${jdbc.pass} - - - - - - - - - src/main/resources - - **/* - - - - - - - - - - src/test/resources - - **/* - - - - - com.monkeyk core - 0.3 - - - - - javax.servlet - javax.servlet-api - 3.1.0 - provided - - - javax.servlet.jsp - jsp-api - 2.1 - provided - - - - - net.sf.ehcache - ehcache-web - 2.0.4 + ${core.version} - - opensymphony - sitemesh - 2.4 - - - org.aspectj - aspectjrt - ${aspectj.version} - compile - - - org.aspectj - aspectjweaver - ${aspectj.version} - compile - - - - commons-dbcp - commons-dbcp - 1.4 - - - - commons-io - commons-io - 2.4 - - - - org.apache.oltu.oauth2 - org.apache.oltu.oauth2.authzserver - ${oltu.version} - org.apache.oltu.oauth2 @@ -228,107 +60,94 @@ - - - - - - - - - org.springframework - spring-aop - ${spring.version} + org.springframework.boot + spring-boot-starter - org.springframework - spring-tx - ${spring.version} - - - org.springframework - spring-expression - ${spring.version} + org.springframework.boot + spring-boot-starter-jdbc - org.springframework - spring-web - ${spring.version} + org.springframework.boot + spring-boot-starter-web - org.springframework - spring-webmvc - ${spring.version} + org.springframework.boot + spring-boot-starter-thymeleaf - - mysql - mysql-connector-java - ${mysql.version} - compile + com.github.theborakompanioni + thymeleaf-extras-shiro + 2.1.0 - - log4j - log4j - 1.2.14 - compile + mysql + mysql-connector-java + - org.slf4j - slf4j-log4j12 - 1.7.5 - compile + com.zaxxer + HikariCP + - javax.servlet - jstl - 1.1.2 - - - taglibs - standard - 1.1.2 - compile + org.springframework.boot + spring-boot-starter-test + test + - - - com.fasterxml.jackson.core - jackson-databind - ${fasterxml.jackson.version} - + + authz + + + org.springframework.boot + spring-boot-maven-plugin + - - - org.springframework - spring-test - ${spring.version} - test - - - org.testng - testng - 6.1.1 - test - - - junit - junit - - - - + + pl.project13.maven + git-commit-id-plugin + 4.9.10 + + + + revision + + + + + true + true + + ^git.remote.origin.url$ + ^git.branch$ + ^git.commit.id$ + ^git.build.time$ + + + + + maven-surefire-plugin + + + ${test.skip} + none + + + + + \ No newline at end of file diff --git a/authz/src/main/java/com/monkeyk/os/AuthzApplication.java b/authz/src/main/java/com/monkeyk/os/AuthzApplication.java new file mode 100644 index 0000000000000000000000000000000000000000..0088b809d416e791ba2d3f5dbac7a7e2a2c33731 --- /dev/null +++ b/authz/src/main/java/com/monkeyk/os/AuthzApplication.java @@ -0,0 +1,20 @@ +package com.monkeyk.os; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * 2020/7/13 + * + * @author Shengzhao Li + * @since 2.0.0 + */ +@SpringBootApplication +public class AuthzApplication { + + + public static void main(String[] args) { + SpringApplication.run(AuthzApplication.class, args); + } + +} diff --git a/authz/src/main/java/com/monkeyk/os/config/AuthzContextConfig.java b/authz/src/main/java/com/monkeyk/os/config/AuthzContextConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..e247bdf308ca9b6b2f6dd9b96e181ceebdd551c6 --- /dev/null +++ b/authz/src/main/java/com/monkeyk/os/config/AuthzContextConfig.java @@ -0,0 +1,66 @@ +package com.monkeyk.os.config; + +import com.monkeyk.os.domain.oauth.AuthenticationIdGenerator; +import com.monkeyk.os.domain.oauth.DefaultAuthenticationIdGenerator; +import org.apache.oltu.oauth2.as.issuer.MD5Generator; +import org.apache.oltu.oauth2.as.issuer.OAuthIssuer; +import org.apache.oltu.oauth2.as.issuer.OAuthIssuerImpl; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.transaction.TransactionManager; + +import javax.sql.DataSource; + +/** + * 2020/7/14 + *

+ * Replace authz-context.xml + * + * @author Shengzhao Li + * @since 2.0.0 + */ +@Configuration +public class AuthzContextConfig { + + + /** + * 事务配置 + */ + @Bean + public TransactionManager transactionManager(DataSource dataSource) { + DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); + transactionManager.setDataSource(dataSource); + return transactionManager; + } + + + @Bean + public JdbcTemplate jdbcTemplate(DataSource dataSource) { + JdbcTemplate jdbcTemplate = new JdbcTemplate(); + jdbcTemplate.setDataSource(dataSource); + return jdbcTemplate; + } + + + /** + * AuthenticationId 的生成器 + */ + @Bean + public AuthenticationIdGenerator authenticationIdGenerator() { + return new DefaultAuthenticationIdGenerator(); + } + + /** + * 默认使用MD5 OAuthIssuer, 生成随机值,如 access_token, refresh_token + * 可根据需要扩展使用其他的实现 + */ + @Bean + public OAuthIssuer oAuthIssuer() { + MD5Generator md5Generator = new MD5Generator(); + return new OAuthIssuerImpl(md5Generator); + } + + +} diff --git a/authz/src/main/java/com/monkeyk/os/config/AuthzSecurityConfig.java b/authz/src/main/java/com/monkeyk/os/config/AuthzSecurityConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..50007935c8178384af6fc764c8912b1076e56cc6 --- /dev/null +++ b/authz/src/main/java/com/monkeyk/os/config/AuthzSecurityConfig.java @@ -0,0 +1,152 @@ +package com.monkeyk.os.config; + +import com.monkeyk.os.infrastructure.shiro.MkkJdbcRealm; +import org.apache.shiro.authc.credential.CredentialsMatcher; +import org.apache.shiro.authc.credential.HashedCredentialsMatcher; +import org.apache.shiro.cache.AbstractCacheManager; +import org.apache.shiro.cache.MemoryConstrainedCacheManager; +import org.apache.shiro.crypto.hash.Sha256Hash; +import org.apache.shiro.mgt.SecurityManager; +import org.apache.shiro.realm.AuthorizingRealm; +import org.apache.shiro.realm.Realm; +import org.apache.shiro.spring.web.ShiroFilterFactoryBean; +import org.apache.shiro.web.mgt.DefaultWebSecurityManager; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.sql.DataSource; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * 2020/7/14 + *

+ * Replace authz-security.xml + * + * @author Shengzhao Li + * @since 2.0.0 + */ +@Configuration +public class AuthzSecurityConfig { + + /* + * 这是一个标准的 SHIRO 安全配置 + 注意OAuth的URL配置: /oauth/** = anon + * + * */ + + /** + * 存储凭证(如密码)的加密算法(如 MD5,SHA-256,SHA-384,SHA-512) + * 默认 SHA-256 + *

+ * 若是旧版本升级后继续使用MD5,请将配置值更新为MD5 + * + * authz.store.credentials.alg=MD5 + * + * + * @since 2.0.0 + */ + @Value("${authz.store.credentials.alg:" + Sha256Hash.ALGORITHM_NAME + "}") + private String storeCredentialsAlg; + + /** + * 使用指定算法 进行密码的加密与匹配 + */ + @Bean + public CredentialsMatcher credentialsMatcher() { + HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); + credentialsMatcher.setHashAlgorithmName(this.storeCredentialsAlg); +// credentialsMatcher.setStoredCredentialsHexEncoded(false); + return credentialsMatcher; + } + + /** + * 扩展的 SHIRO Realm + * 使用JDBC实现, 并添加 逻辑删除 (archived = 0) 的处理 + */ + @Bean + public AuthorizingRealm jdbcRealm(DataSource dataSource) { + MkkJdbcRealm realm = new MkkJdbcRealm(); + realm.setName("jdbcRealm"); + realm.setDataSource(dataSource); + realm.setCredentialsMatcher(credentialsMatcher()); + realm.setPermissionsLookupEnabled(true); + return realm; + } + + /** + * 使用基于内存的缓存 SHIRO 相关数据 + */ + @Bean + public AbstractCacheManager shiroCacheManager() { + return new MemoryConstrainedCacheManager(); + } + + /** + * SHIRO SecurityManager 配置 + */ + @Bean + public DefaultWebSecurityManager securityManager(Realm realm) { + DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); + securityManager.setRealm(realm); + securityManager.setCacheManager(shiroCacheManager()); + return securityManager; + } + + + /** + * SHIRO安全机制拦截器 Filter实现, 注意id必须与 web.xml 中的 shiroFilter 一致 + */ + @Bean + public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { + ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean(); + factoryBean.setSecurityManager(securityManager); + factoryBean.setLoginUrl("/login"); + factoryBean.setSuccessUrl("/index"); + factoryBean.setUnauthorizedUrl("/unauthorized"); + //权限控制, map必须要有顺序 + Map map = new LinkedHashMap<>(); + map.put("/favicon.ico", "anon"); + map.put("/css/**", "anon"); + map.put("/js/**", "anon"); + map.put("/html/**", "anon"); + map.put("/login", "anon"); + map.put("/unauthorized", "anon"); + // # OAuth anon + map.put("/oauth/**", "anon"); + map.put("/users/**", "anon"); + map.put("/client_details*", "anon"); + map.put("/client_details/**", "anon"); + map.put("/logout", "logout"); + //# admin role + map.put("/admin/**", "authc, roles[\"Admin\"]"); + // #user permissions + map.put("/user/list", "authc, perms[\"user:list\"]"); + map.put("/user/create", "authc, perms[\"user:create\"]"); + // # everything else requires authentication: + map.put("/**", "authc"); + factoryBean.setFilterChainDefinitionMap(map); +// factoryBean.setFilterChainDefinitions(" /favicon.ico = anon\n" + +// " /resources/** = anon\n" + +// " /statics/** = anon\n" + +// " /login = anon\n" + +// " /unauthorized = anon\n" + +// " # OAuth anon\n" + +// " /oauth/** = anon\n" + +// " /users/** = anon\n" + +// " /client_details* = anon\n" + +// " /client_details/** = anon\n" + +// " /logout = logout\n" + +// " # admin role\n" + +// " /admin/** = authc, roles[\"Admin\"]\n" + +// " #user permissions\n" + +// " /user/list = authc, perms[\"user:list\"]\n" + +// " /user/create = authc, perms[\"user:create\"]\n" + +// " # everything else requires authentication:\n" + +// " /** = authc"); + return factoryBean; + } + + +} diff --git a/authz/src/main/java/com/monkeyk/os/config/AuthzWebConfig.java b/authz/src/main/java/com/monkeyk/os/config/AuthzWebConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..46479d05be4baf6d9ec6cd47110bb8934652e5d3 --- /dev/null +++ b/authz/src/main/java/com/monkeyk/os/config/AuthzWebConfig.java @@ -0,0 +1,97 @@ +package com.monkeyk.os.config; + +import at.pollux.thymeleaf.shiro.dialect.ShiroDialect; +import com.monkeyk.os.web.context.MkkCharacterEncodingFilter; +import com.monkeyk.os.web.context.OAuthShiroHandlerExceptionResolver; +import org.apache.shiro.mgt.SecurityManager; +import org.apache.shiro.spring.LifecycleBeanPostProcessor; +import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.HandlerExceptionResolver; + +/** + * 2020/7/14 + *

+ * Replace web.xml + * mkk-servlet.xml + * + * @author Shengzhao Li + * @since 2.0.0 + */ +@Configuration +public class AuthzWebConfig { + + + /** + * 字符编码配置 UTF-8 + */ + @Bean + public FilterRegistrationBean encodingFilter() { + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + registrationBean.setFilter(new MkkCharacterEncodingFilter()); + registrationBean.addUrlPatterns("/*"); + //值越小越靠前 + registrationBean.setOrder(1); + return registrationBean; + } + + +// /** +// Shiro Filter +// SHIRO 安全过滤器 配置 +// */ +// @Bean +// public DelegatingFilterProxy shiroFilter(){ +// DelegatingFilterProxy filterProxy=new DelegatingFilterProxy(); +// filterProxy.setTargetFilterLifecycle(true); +// +// return filterProxy; +// } + + + /** + * 异常处理配置 + */ + @Bean + public HandlerExceptionResolver handlerExceptionResolver() { + return new OAuthShiroHandlerExceptionResolver(); + } + + /** + * Shiro AOP + */ + @Bean + public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { + return new LifecycleBeanPostProcessor(); + } + + /** + * Enable Shiro Annotations for Spring-configured beans. Only run after + * the lifecycleBeanProcessor has run: + */ +// public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){ +// DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator(); +// autoProxyCre +// return autoProxyCreator; +// } + @Bean + public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { + AuthorizationAttributeSourceAdvisor sourceAdvisor = new AuthorizationAttributeSourceAdvisor(); + sourceAdvisor.setSecurityManager(securityManager); + return sourceAdvisor; + } + + + /** + * shiro with thymeleaf ext + * + * @since 2.0.0 + */ + @Bean + public ShiroDialect shiroDialect() { + return new ShiroDialect(); + } + +} diff --git a/authz/src/main/java/com/monkeyk/os/domain/users/AuthzPasswordEncoder.java b/authz/src/main/java/com/monkeyk/os/domain/users/AuthzPasswordEncoder.java new file mode 100644 index 0000000000000000000000000000000000000000..706e27e9e16a4de7a91691b24d033a651e0bcf34 --- /dev/null +++ b/authz/src/main/java/com/monkeyk/os/domain/users/AuthzPasswordEncoder.java @@ -0,0 +1,105 @@ +package com.monkeyk.os.domain.users; + +import com.monkeyk.os.domain.users.password.MD5PasswordEncoder; +import com.monkeyk.os.domain.users.password.PasswordEncoder; +import com.monkeyk.os.domain.users.password.ShaPasswordEncoder; +import org.apache.shiro.crypto.hash.*; +import org.apache.shiro.util.Assert; +import org.apache.shiro.util.ByteSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + * 2023/9/25 17:15 + *

+ * 对各类密码的 加密,校验封装 + * + * @author Shengzhao Li + * @since 2.0.0 + */ +@Component +public class AuthzPasswordEncoder implements InitializingBean { + + private static final Logger LOG = LoggerFactory.getLogger(AuthzPasswordEncoder.class); + + /** + * 存储凭证(如密码)的加密算法(如 MD5,SHA-256,SHA-384,SHA-512) + * 默认 SHA-256 + *

+ * 若是旧版本升级后继续使用MD5,请将配置值更新为MD5 + * + * authz.store.credentials.alg=MD5 + * + * + * @since 2.0.0 + */ + @Value("${authz.store.credentials.alg:" + Sha256Hash.ALGORITHM_NAME + "}") + private String storeCredentialsAlg; + + + private PasswordEncoder passwordEncoder; + + + public AuthzPasswordEncoder() { + } + + + /** + * 加密 + * + * @param rasPassword 原始密码 + * @param salt 盐 or null + * @return 加密后的 + */ + public String encode(String rasPassword, ByteSource salt) { + return this.passwordEncoder.encode(rasPassword, salt); + } + + /** + * 校验密码是否正确 + * + * @param rawPassword 原始密码 + * @param encodedPassword 加密的密码 + * @param salt 盐 or null + * @return true 校验正确,其他 false + */ + public boolean matches(String rawPassword, String encodedPassword, ByteSource salt) { + return this.passwordEncoder.matches(rawPassword, encodedPassword, salt); + } + + + @Override + public void afterPropertiesSet() throws Exception { + Assert.notNull(this.storeCredentialsAlg, "storeCredentialsAlg is null"); + + switch (this.storeCredentialsAlg) { + case Sha256Hash.ALGORITHM_NAME: + this.passwordEncoder = new ShaPasswordEncoder(256); + break; + case Sha384Hash.ALGORITHM_NAME: + this.passwordEncoder = new ShaPasswordEncoder(384); + break; + case Sha512Hash.ALGORITHM_NAME: + this.passwordEncoder = new ShaPasswordEncoder(512); + break; + case Sha1Hash.ALGORITHM_NAME: + this.passwordEncoder = new ShaPasswordEncoder(1); + if (LOG.isWarnEnabled()) { + LOG.warn("[Deprecated] alg: {} is unsafe, not recommended for use (try use SHA-256 or SHA-512 much better)", Sha1Hash.ALGORITHM_NAME); + } + break; + case Md5Hash.ALGORITHM_NAME: + this.passwordEncoder = new MD5PasswordEncoder(); + if (LOG.isWarnEnabled()) { + LOG.warn("[Deprecated] alg: {} is unsafe, not recommended for use (try use SHA-256 or SHA-512 much better)", Md5Hash.ALGORITHM_NAME); + } + break; + default: + throw new IllegalArgumentException("Unsupport storeCredentialsAlg: " + + this.storeCredentialsAlg + ", please checking property 'authz.store.credentials.alg' "); + } + } +} diff --git a/authz/src/main/java/com/monkeyk/os/domain/users/PasswordHandler.java b/authz/src/main/java/com/monkeyk/os/domain/users/PasswordHandler.java index d316eaaba8973ed25c04ba4c63bb1f82ee76e0dc..95867ba0e99cbc0475e6aee94761318d809ed25f 100644 --- a/authz/src/main/java/com/monkeyk/os/domain/users/PasswordHandler.java +++ b/authz/src/main/java/com/monkeyk/os/domain/users/PasswordHandler.java @@ -3,6 +3,7 @@ package com.monkeyk.os.domain.users; import java.io.UnsupportedEncodingException; import java.math.BigInteger; +import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -13,6 +14,7 @@ import java.security.NoSuchAlgorithmException; * 使用MD5 * * @author Shengzhao Li + * @deprecated use AuthzPasswordEncoder.java replaced from 2.0.0 */ public abstract class PasswordHandler { @@ -30,12 +32,7 @@ public abstract class PasswordHandler { throw new IllegalStateException("MD5 algorithm not available. Fatal (should be in the JDK)."); } - try { - byte[] bytes = digest.digest(password.getBytes("UTF-8")); - return String.format("%032x", new BigInteger(1, bytes)); - } catch (UnsupportedEncodingException e) { - throw new IllegalStateException("UTF-8 encoding not available. Fatal (should be in the JDK)."); - } - + byte[] bytes = digest.digest(password.getBytes(StandardCharsets.UTF_8)); + return String.format("%032x", new BigInteger(1, bytes)); } } diff --git a/authz/src/main/java/com/monkeyk/os/domain/users/password/MD5PasswordEncoder.java b/authz/src/main/java/com/monkeyk/os/domain/users/password/MD5PasswordEncoder.java new file mode 100644 index 0000000000000000000000000000000000000000..b8ef47718e0edc4ccf79768e83664fe9fedd903f --- /dev/null +++ b/authz/src/main/java/com/monkeyk/os/domain/users/password/MD5PasswordEncoder.java @@ -0,0 +1,28 @@ +package com.monkeyk.os.domain.users.password; + +import org.apache.shiro.crypto.hash.Md5Hash; +import org.apache.shiro.util.ByteSource; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; + +/** + * 2023/9/25 16:23 + * + * @author Shengzhao Li + * @since 2.0.0 + */ +public class MD5PasswordEncoder implements PasswordEncoder { + + @Override + public String encode(String rawPassword, ByteSource salt) { + Md5Hash md5Hash = new Md5Hash(rawPassword, salt); + return md5Hash.toHex(); + } + + @Override + public boolean matches(String rawPassword, String encodedPassword, ByteSource salt) { + String encode = this.encode(rawPassword, salt); + return MessageDigest.isEqual(encode.getBytes(StandardCharsets.UTF_8), encodedPassword.getBytes(StandardCharsets.UTF_8)); + } +} diff --git a/authz/src/main/java/com/monkeyk/os/domain/users/password/PasswordEncoder.java b/authz/src/main/java/com/monkeyk/os/domain/users/password/PasswordEncoder.java new file mode 100644 index 0000000000000000000000000000000000000000..99aa064215d9356768796feb7206b0756925864f --- /dev/null +++ b/authz/src/main/java/com/monkeyk/os/domain/users/password/PasswordEncoder.java @@ -0,0 +1,36 @@ +package com.monkeyk.os.domain.users.password; + +import org.apache.shiro.util.ByteSource; + +/** + * 2023/9/25 16:19 + *

+ * 密码加密 与 验证 + * + * @author Shengzhao Li + * @since 2.0.0 + */ +public interface PasswordEncoder { + + + /** + * 加密密码 + * + * @param rawPassword 原始密码 + * @param salt 盐 or null + * @return 加密后的密码 + */ + String encode(String rawPassword, ByteSource salt); + + + /** + * 校验加密的密码是否正确 + * + * @param rawPassword 原始密码 + * @param encodedPassword 加密的密码 + * @param salt 盐 or null + * @return true 校验正确,其他 false + */ + boolean matches(String rawPassword, String encodedPassword, ByteSource salt); + +} diff --git a/authz/src/main/java/com/monkeyk/os/domain/users/password/ShaPasswordEncoder.java b/authz/src/main/java/com/monkeyk/os/domain/users/password/ShaPasswordEncoder.java new file mode 100644 index 0000000000000000000000000000000000000000..3c8c84c422e26486b096b746998d2201d1ac1f27 --- /dev/null +++ b/authz/src/main/java/com/monkeyk/os/domain/users/password/ShaPasswordEncoder.java @@ -0,0 +1,66 @@ +package com.monkeyk.os.domain.users.password; + +import org.apache.shiro.crypto.hash.SimpleHash; +import org.apache.shiro.util.ByteSource; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; + +/** + * 2023/9/25 16:47 + *

+ * SHA-xx 算法实现抽象, + * 如SHA-256 + * + * @author Shengzhao Li + * @since 2.0.0 + */ +public class ShaPasswordEncoder implements PasswordEncoder { + + private static final String SHA_PREFIX = "SHA-"; + + /** + * 如 256, 384, 512 + */ + private int length; + + /** + * Alg + */ + private final String alg; + + /** + * SHA算法 + * + * @param length 如 256, 384, 512 + */ + public ShaPasswordEncoder(int length) { + this.alg = SHA_PREFIX + length; + + } + + + /** + * SHA算法 + * + * @param alg 如 SHA-256, SHA-384, SHA-512 + */ + public ShaPasswordEncoder(String alg) { + this.alg = alg; + } + + @Override + public String encode(String rawPassword, ByteSource salt) { + char[] pwdChar = rawPassword.toCharArray(); + SimpleHash simpleHash = new SimpleHash(this.alg, pwdChar, salt); + return simpleHash.toHex(); + } + + @Override + public boolean matches(String rawPassword, String encodedPassword, ByteSource salt) { + String encode = this.encode(rawPassword, salt); + return MessageDigest.isEqual( + encode.getBytes(StandardCharsets.UTF_8), + encodedPassword.getBytes(StandardCharsets.UTF_8)); + } +} diff --git a/authz/src/main/java/com/monkeyk/os/infrastructure/jdbc/OauthJdbcRepository.java b/authz/src/main/java/com/monkeyk/os/infrastructure/jdbc/OauthJdbcRepository.java index 72cb939cc801531211cf2eee416a7bb2d7374d0a..f4a1a07aea7fd044671199c8f75c3ebf1c2094ab 100644 --- a/authz/src/main/java/com/monkeyk/os/infrastructure/jdbc/OauthJdbcRepository.java +++ b/authz/src/main/java/com/monkeyk/os/infrastructure/jdbc/OauthJdbcRepository.java @@ -23,10 +23,10 @@ import java.util.List; public class OauthJdbcRepository extends AbstractJdbcRepository implements OauthRepository { - private static ClientDetailsRowMapper clientDetailsRowMapper = new ClientDetailsRowMapper(); - private static OauthCodeRowMapper oauthCodeRowMapper = new OauthCodeRowMapper(); + private final ClientDetailsRowMapper clientDetailsRowMapper = new ClientDetailsRowMapper(); + private final OauthCodeRowMapper oauthCodeRowMapper = new OauthCodeRowMapper(); - private static AccessTokenRowMapper accessTokenRowMapper = new AccessTokenRowMapper(); + private final AccessTokenRowMapper accessTokenRowMapper = new AccessTokenRowMapper(); @Override @@ -41,42 +41,36 @@ public class OauthJdbcRepository extends AbstractJdbcRepository implements Oauth final String sql = " insert into oauth_client_details(client_id,client_secret,client_name, client_uri,client_icon_uri,resource_ids, scope,grant_types, " + "redirect_uri,roles,access_token_validity,refresh_token_validity,description,archived,trusted) values (?,?,?, ?,?,?,?,?, ?,?, ?,? ,?,?,?)"; - return jdbcTemplate.update(sql, new PreparedStatementSetter() { - @Override - public void setValues(PreparedStatement ps) throws SQLException { - ps.setString(1, clientDetails.getClientId()); - ps.setString(2, clientDetails.getClientSecret()); - ps.setString(3, clientDetails.getName()); - - ps.setString(4, clientDetails.getClientUri()); - ps.setString(5, clientDetails.getIconUri()); - ps.setString(6, clientDetails.resourceIds()); - - ps.setString(7, clientDetails.scope()); - ps.setString(8, clientDetails.grantTypes()); - ps.setString(9, clientDetails.getRedirectUri()); - - ps.setString(10, clientDetails.roles()); - ps.setInt(11, clientDetails.accessTokenValidity() == null ? -1 : clientDetails.accessTokenValidity()); - ps.setInt(12, clientDetails.refreshTokenValidity() == null ? -1 : clientDetails.refreshTokenValidity()); - - ps.setString(13, clientDetails.getDescription()); - ps.setBoolean(14, clientDetails.archived()); - ps.setBoolean(15, clientDetails.trusted()); - } + return jdbcTemplate.update(sql, ps -> { + ps.setString(1, clientDetails.getClientId()); + ps.setString(2, clientDetails.getClientSecret()); + ps.setString(3, clientDetails.getName()); + + ps.setString(4, clientDetails.getClientUri()); + ps.setString(5, clientDetails.getIconUri()); + ps.setString(6, clientDetails.resourceIds()); + + ps.setString(7, clientDetails.scope()); + ps.setString(8, clientDetails.grantTypes()); + ps.setString(9, clientDetails.getRedirectUri()); + + ps.setString(10, clientDetails.roles()); + ps.setInt(11, clientDetails.accessTokenValidity() == null ? -1 : clientDetails.accessTokenValidity()); + ps.setInt(12, clientDetails.refreshTokenValidity() == null ? -1 : clientDetails.refreshTokenValidity()); + + ps.setString(13, clientDetails.getDescription()); + ps.setBoolean(14, clientDetails.archived()); + ps.setBoolean(15, clientDetails.trusted()); }); } @Override public int saveOauthCode(final OauthCode oauthCode) { final String sql = " insert into oauth_code(code,username,client_id) values (?,?,?)"; - return jdbcTemplate.update(sql, new PreparedStatementSetter() { - @Override - public void setValues(PreparedStatement ps) throws SQLException { - ps.setString(1, oauthCode.code()); - ps.setString(2, oauthCode.username()); - ps.setString(3, oauthCode.clientId()); - } + return jdbcTemplate.update(sql, ps -> { + ps.setString(1, oauthCode.code()); + ps.setString(2, oauthCode.username()); + ps.setString(3, oauthCode.clientId()); }); } @@ -97,13 +91,10 @@ public class OauthJdbcRepository extends AbstractJdbcRepository implements Oauth @Override public int deleteOauthCode(final OauthCode oauthCode) { final String sql = " delete from oauth_code where code = ? and client_id = ? and username = ?"; - return jdbcTemplate.update(sql, new PreparedStatementSetter() { - @Override - public void setValues(PreparedStatement ps) throws SQLException { - ps.setString(1, oauthCode.code()); - ps.setString(2, oauthCode.clientId()); - ps.setString(3, oauthCode.username()); - } + return jdbcTemplate.update(sql, ps -> { + ps.setString(1, oauthCode.code()); + ps.setString(2, oauthCode.clientId()); + ps.setString(3, oauthCode.username()); }); } @@ -117,13 +108,10 @@ public class OauthJdbcRepository extends AbstractJdbcRepository implements Oauth @Override public int deleteAccessToken(final AccessToken accessToken) { final String sql = " delete from oauth_access_token where client_id = ? and username = ? and authentication_id = ?"; - return jdbcTemplate.update(sql, new PreparedStatementSetter() { - @Override - public void setValues(PreparedStatement ps) throws SQLException { - ps.setString(1, accessToken.clientId()); - ps.setString(2, accessToken.username()); - ps.setString(3, accessToken.authenticationId()); - } + return jdbcTemplate.update(sql, ps -> { + ps.setString(1, accessToken.clientId()); + ps.setString(2, accessToken.username()); + ps.setString(3, accessToken.authenticationId()); }); } @@ -132,20 +120,17 @@ public class OauthJdbcRepository extends AbstractJdbcRepository implements Oauth final String sql = "insert into oauth_access_token(token_id,token_expired_seconds,authentication_id," + "username,client_id,token_type,refresh_token_expired_seconds,refresh_token) values (?,?,?,?,?,?,?,?) "; - return jdbcTemplate.update(sql, new PreparedStatementSetter() { - @Override - public void setValues(PreparedStatement ps) throws SQLException { - ps.setString(1, accessToken.tokenId()); - ps.setInt(2, accessToken.tokenExpiredSeconds()); - ps.setString(3, accessToken.authenticationId()); + return jdbcTemplate.update(sql, ps -> { + ps.setString(1, accessToken.tokenId()); + ps.setInt(2, accessToken.tokenExpiredSeconds()); + ps.setString(3, accessToken.authenticationId()); - ps.setString(4, accessToken.username()); - ps.setString(5, accessToken.clientId()); - ps.setString(6, accessToken.tokenType()); + ps.setString(4, accessToken.username()); + ps.setString(5, accessToken.clientId()); + ps.setString(6, accessToken.tokenType()); - ps.setInt(7, accessToken.refreshTokenExpiredSeconds()); - ps.setString(8, accessToken.refreshToken()); - } + ps.setInt(7, accessToken.refreshTokenExpiredSeconds()); + ps.setString(8, accessToken.refreshToken()); }); } diff --git a/authz/src/main/java/com/monkeyk/os/infrastructure/jdbc/UsersJdbcAuthzRepository.java b/authz/src/main/java/com/monkeyk/os/infrastructure/jdbc/UsersJdbcAuthzRepository.java index f9ebb053663b6677527e962f23163c6a78c3fba4..f1f1dbe02cf3c12f7df5cfa53d78861d5bd4c557 100644 --- a/authz/src/main/java/com/monkeyk/os/infrastructure/jdbc/UsersJdbcAuthzRepository.java +++ b/authz/src/main/java/com/monkeyk/os/infrastructure/jdbc/UsersJdbcAuthzRepository.java @@ -4,6 +4,8 @@ import com.monkeyk.os.domain.users.Roles; import com.monkeyk.os.domain.users.Users; import com.monkeyk.os.domain.users.UsersAuthzRepository; import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.jdbc.core.PreparedStatementSetter; import org.springframework.stereotype.Repository; @@ -22,9 +24,10 @@ import java.util.List; @Repository("usersJdbcAuthzRepository") public class UsersJdbcAuthzRepository extends AbstractJdbcRepository implements UsersAuthzRepository { + private static final Logger LOG = LoggerFactory.getLogger(UsersJdbcAuthzRepository.class); - private static UsersRowMapper usersRowMapper = new UsersRowMapper(); - private static RolesRowMapper rolesRowMapper = new RolesRowMapper(); + private final UsersRowMapper usersRowMapper = new UsersRowMapper(); + private final RolesRowMapper rolesRowMapper = new RolesRowMapper(); @Override @@ -54,19 +57,20 @@ public class UsersJdbcAuthzRepository extends AbstractJdbcRepository implements @Override public int saveUsers(final Users users) { - String sql = " insert into users(guid,create_time, username,password) values (?,?,?,?) "; - this.jdbcTemplate.update(sql, new PreparedStatementSetter() { - @Override - public void setValues(PreparedStatement ps) throws SQLException { - ps.setString(1, users.guid()); - ps.setTimestamp(2, new Timestamp(users.createTime().getTime())); - ps.setString(3, users.username()); - - ps.setString(4, users.password()); - } + String sql = " insert into users(guid,create_time, username,password, password_salt) values (?,?,?,?,?) "; + int row = this.jdbcTemplate.update(sql, ps -> { + ps.setString(1, users.guid()); + ps.setTimestamp(2, new Timestamp(users.createTime().getTime())); + ps.setString(3, users.username()); + + ps.setString(4, users.password()); + ps.setString(5, users.passwordSalt()); }); + if (LOG.isDebugEnabled()) { + LOG.debug("Insert into users -> row: {}", row); + } - return this.jdbcTemplate.queryForObject("select id from users where guid = ?", new Object[]{users.guid()}, Integer.class); + return this.jdbcTemplate.queryForObject("select id from users where guid = ?", Integer.class, new Object[]{users.guid()}); } @Override @@ -78,13 +82,13 @@ public class UsersJdbcAuthzRepository extends AbstractJdbcRepository implements @Override public void insertUserRoles(final int userId, final int rolesId) { String sql = "insert into user_roles(users_id,roles_id) values (?,?) "; - this.jdbcTemplate.update(sql, new PreparedStatementSetter() { - @Override - public void setValues(PreparedStatement ps) throws SQLException { - ps.setInt(1, userId); - ps.setInt(2, rolesId); - } + int row = this.jdbcTemplate.update(sql, ps -> { + ps.setInt(1, userId); + ps.setInt(2, rolesId); }); + if (LOG.isDebugEnabled()) { + LOG.debug("Insert into user_roles -> row: {}", row); + } } @Override @@ -99,6 +103,6 @@ public class UsersJdbcAuthzRepository extends AbstractJdbcRepository implements public List findPermissionsByRoles(String rolesGuid) { String sql = " select p.permission from roles_permissions p where p.roles_id = (" + " select id from roles where guid = ? and archived = 0 )"; - return this.jdbcTemplate.queryForList(sql, new Object[]{rolesGuid}, String.class); + return this.jdbcTemplate.queryForList(sql, String.class, new Object[]{rolesGuid}); } } diff --git a/authz/src/main/java/com/monkeyk/os/oauth/token/AuthorizationCodeTokenHandler.java b/authz/src/main/java/com/monkeyk/os/oauth/token/AuthorizationCodeTokenHandler.java index 7214f2dd5bb2ac013c88312bf48c68bf56dc2363..1ff5bb3d7fed42b49d51285e4ff4959486967f81 100644 --- a/authz/src/main/java/com/monkeyk/os/oauth/token/AuthorizationCodeTokenHandler.java +++ b/authz/src/main/java/com/monkeyk/os/oauth/token/AuthorizationCodeTokenHandler.java @@ -40,10 +40,9 @@ public class AuthorizationCodeTokenHandler extends AbstractOAuthTokenHandler { return GrantType.AUTHORIZATION_CODE.toString().equalsIgnoreCase(grantType); } - /* - * - * /oauth/token?client_id=unity-client&client_secret=unity&grant_type=authorization_code&code=zLl170&redirect_uri=redirect_uri - * */ + /** + * /oauth/token?client_id=unity-client&client_secret=unity&grant_type=authorization_code&code=zLl170&redirect_uri=redirect_uri + */ @Override public void handleAfterValidation() throws OAuthProblemException, OAuthSystemException { diff --git a/authz/src/main/java/com/monkeyk/os/oauth/token/JwtTokenEnhancer.java b/authz/src/main/java/com/monkeyk/os/oauth/token/JwtTokenEnhancer.java new file mode 100644 index 0000000000000000000000000000000000000000..66b3d423279d9298f7aac722fb1ab6cf7c0eb0ac --- /dev/null +++ b/authz/src/main/java/com/monkeyk/os/oauth/token/JwtTokenEnhancer.java @@ -0,0 +1,115 @@ +package com.monkeyk.os.oauth.token; + +import org.jose4j.jws.AlgorithmIdentifiers; +import org.jose4j.jws.JsonWebSignature; +import org.jose4j.jwt.JwtClaims; +import org.jose4j.jwt.NumericDate; +import org.jose4j.keys.HmacKey; +import org.jose4j.lang.JoseException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.Map; + +import static com.monkeyk.os.domain.oauth.Constants.DEFAULT_KEY_ID; + +/** + * 2023/9/26 15:22 + *

+ * JWT token 增强实现 + * + * @author Shengzhao Li + * @since 2.0.0 + */ +@Component +public class JwtTokenEnhancer implements InitializingBean { + + private static final Logger LOG = LoggerFactory.getLogger(JwtTokenEnhancer.class); + + + /** + * Jwt hamc key ; 长度至少32位 + * TODO: 不同的部署环境请使用不同的hmac key + * + * @since 2.0.0 + */ + @Value("${authz.token.jwt.hmac.key:Bl0depAUL2DRPZnR0DJThK9a9KSJF4Xr}") + private String jwtHmacKey; + + + private HmacKey hmacKey; + + + public JwtTokenEnhancer() { + } + + + /** + * enhance jti + * + * @param jti token + * @param subject payload subject + * @param audience payload audience + * @param expiredSeconds token expired seconds + * @return jwt + */ + public String enhance(String jti, String subject, String audience, int expiredSeconds) { + return this.enhance(jti, subject, audience, expiredSeconds, Collections.emptyMap()); + } + + + /** + * enhance jti + * + * @param jti token + * @param subject payload subject + * @param audience payload audience + * @param expiredSeconds token expired seconds + * @param extPayloadMap ext payload map + * @return jwt + */ + public String enhance(String jti, String subject, String audience, int expiredSeconds, Map extPayloadMap) { + + JsonWebSignature jws = new JsonWebSignature(); + jws.setKeyIdHeaderValue(DEFAULT_KEY_ID); + jws.setKey(hmacKey); + jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.HMAC_SHA256); + + JwtClaims claims = new JwtClaims(); + claims.setSubject(subject); + claims.setJwtId(jti); +// claims.setIssuer("https://myoidc.com"); + claims.setIssuedAtToNow(); + + NumericDate now = NumericDate.now(); + now.addSeconds(expiredSeconds); + claims.setExpirationTime(now); + claims.setAudience(audience); + + for (String key : extPayloadMap.keySet()) { + claims.setStringClaim(key, extPayloadMap.get(key)); + } + + jws.setPayload(claims.toJson()); + + try { + return jws.getCompactSerialization(); + } catch (JoseException e) { + throw new IllegalStateException("Jwt enhance error", e); + } + } + + + @Override + public void afterPropertiesSet() throws Exception { + this.hmacKey = new HmacKey(jwtHmacKey.getBytes(StandardCharsets.UTF_8)); + if (LOG.isDebugEnabled()) { + LOG.debug("Initialized hmacKey: {}", this.hmacKey); + } + } +} diff --git a/authz/src/main/java/com/monkeyk/os/service/business/UsersFormSaver.java b/authz/src/main/java/com/monkeyk/os/service/business/UsersFormSaver.java index 83e6a7ab3a79fffa93e722bddfbfa4f7b0c63904..12941f4845e8ec6681147564bf9b580b74144a6a 100644 --- a/authz/src/main/java/com/monkeyk/os/service/business/UsersFormSaver.java +++ b/authz/src/main/java/com/monkeyk/os/service/business/UsersFormSaver.java @@ -1,10 +1,14 @@ package com.monkeyk.os.service.business; -import com.monkeyk.os.domain.shared.BeanProvider; +import com.monkeyk.os.domain.users.AuthzPasswordEncoder; import com.monkeyk.os.domain.users.Roles; import com.monkeyk.os.domain.users.Users; import com.monkeyk.os.domain.users.UsersAuthzRepository; import com.monkeyk.os.service.dto.UsersFormDto; +import org.apache.shiro.codec.Base64; +import org.apache.shiro.util.ByteSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; import java.util.List; @@ -13,20 +17,27 @@ import java.util.List; * * @author Shengzhao Li */ +@Component public class UsersFormSaver { - private transient UsersAuthzRepository usersRepository = BeanProvider.getBean(UsersAuthzRepository.class); + @Autowired + private UsersAuthzRepository usersRepository; + @Autowired + private AuthzPasswordEncoder passwordEncoder; - private UsersFormDto formDto; - public UsersFormSaver(UsersFormDto formDto) { - this.formDto = formDto; + public UsersFormSaver() { } - public String save() { + + public String save(UsersFormDto formDto) { Users users = formDto.newUsers(); + //用指定的加密方式 + ByteSource salt = ByteSource.Util.bytes(Base64.decode(users.passwordSalt())); + users.password(this.passwordEncoder.encode(formDto.getPassword(), salt)); + final int id = usersRepository.saveUsers(users); final List roleGuids = formDto.getRoleGuids(); diff --git a/authz/src/main/java/com/monkeyk/os/service/dto/ClientDetailsFormDto.java b/authz/src/main/java/com/monkeyk/os/service/dto/ClientDetailsFormDto.java index af0a1baa4e13af9ac8744e5b70e3bdb3048d31bf..3dc0984a08f8711d54a84e0eea5801731a344d86 100644 --- a/authz/src/main/java/com/monkeyk/os/service/dto/ClientDetailsFormDto.java +++ b/authz/src/main/java/com/monkeyk/os/service/dto/ClientDetailsFormDto.java @@ -35,6 +35,7 @@ public class ClientDetailsFormDto extends ClientDetailsDto { public ClientDetailsFormDto(List rolesList) { this(); + this.setGrantTypes(""); this.rolesDtoList = RolesDto.toDtos(rolesList); } diff --git a/authz/src/main/java/com/monkeyk/os/service/dto/UsersFormDto.java b/authz/src/main/java/com/monkeyk/os/service/dto/UsersFormDto.java index 092acaf8424aa83c48a9509a517bb159840c264e..93ceb871828d1c5de8707a3c0214ad5fd25e850b 100644 --- a/authz/src/main/java/com/monkeyk/os/service/dto/UsersFormDto.java +++ b/authz/src/main/java/com/monkeyk/os/service/dto/UsersFormDto.java @@ -1,7 +1,6 @@ package com.monkeyk.os.service.dto; import com.monkeyk.os.domain.shared.GuidGenerator; -import com.monkeyk.os.domain.users.PasswordHandler; import com.monkeyk.os.domain.users.Users; import java.util.ArrayList; @@ -39,7 +38,9 @@ public class UsersFormDto extends UsersDto { public Users newUsers() { return new Users() .username(getUsername()) - .password(PasswordHandler.md5(getPassword())) +// .password(PasswordHandler.md5(getPassword())) + // salt, since 2.0.0 + .passwordSalt(GuidGenerator.nextSaltHex()) .guid(GuidGenerator.generate()); } } diff --git a/authz/src/main/java/com/monkeyk/os/service/impl/OauthServiceImpl.java b/authz/src/main/java/com/monkeyk/os/service/impl/OauthServiceImpl.java index 3b61bd2667f0ee024303028cc5d007e4fcc37768..799d3cccdc27290062ab25786ac5a3be39a624e7 100644 --- a/authz/src/main/java/com/monkeyk/os/service/impl/OauthServiceImpl.java +++ b/authz/src/main/java/com/monkeyk/os/service/impl/OauthServiceImpl.java @@ -1,6 +1,7 @@ package com.monkeyk.os.service.impl; import com.monkeyk.os.domain.oauth.*; +import com.monkeyk.os.oauth.token.JwtTokenEnhancer; import com.monkeyk.os.service.OauthService; import org.apache.oltu.oauth2.as.issuer.OAuthIssuer; import org.apache.oltu.oauth2.common.exception.OAuthSystemException; @@ -9,6 +10,7 @@ import org.apache.shiro.SecurityUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.util.Set; @@ -31,6 +33,29 @@ public class OauthServiceImpl implements OauthService { @Autowired private OAuthIssuer oAuthIssuer; + + /** + * 生成token的类型是 jwt 还是 md5 + * + *

+ * 根据配置参数authz.token.generator.type 来决定是用 jwt 还是 md5值 + * 可选值:md5 或 jwt (默认) + * + * @since 2.0.0 + */ + @Value("${authz.token.generator.type:jwt}") + private String tokenGeneratorType; + + + /** + * 增加 token 为 jwt 格式 + * + * @since 2.0.0 + */ + @Autowired + private JwtTokenEnhancer jwtTokenEnhancer; + + @Override public ClientDetails loadClientDetails(String clientId) { LOG.debug("Load ClientDetails by clientId: {}", clientId); @@ -210,12 +235,12 @@ public class OauthServiceImpl implements OauthService { return oauthRepository.findAccessTokenByRefreshToken(refreshToken, clientId); } - /* - * Get AccessToken - * Generate a new AccessToken from existed(exclude token,refresh_token) - * Update access_token,refresh_token, expired. - * Save and remove old - * */ + /** + * Get AccessToken + * Generate a new AccessToken from existed(exclude token,refresh_token) + * Update access_token,refresh_token, expired. + * Save and remove old + */ @Override public AccessToken changeAccessTokenByRefreshToken(String refreshToken, String clientId) throws OAuthSystemException { final AccessToken oldToken = loadAccessTokenByRefreshToken(refreshToken, clientId); @@ -227,8 +252,12 @@ public class OauthServiceImpl implements OauthService { newAccessToken.updateByClientDetails(details); final String authId = authenticationIdGenerator.generate(clientId, oldToken.username(), null); + String tokenId = oAuthIssuer.accessToken(); + if (needEnhanceJwt()) { + tokenId = jwtTokenEnhancer.enhance(tokenId, oldToken.username(), clientId, newAccessToken.tokenExpiredSeconds()); + } newAccessToken.authenticationId(authId) - .tokenId(oAuthIssuer.accessToken()) + .tokenId(tokenId) .refreshToken(oAuthIssuer.refreshToken()); oauthRepository.deleteAccessToken(oldToken); @@ -246,14 +275,22 @@ public class OauthServiceImpl implements OauthService { return clientDetails != null; } - private AccessToken createAndSaveAccessToken(ClientDetails clientDetails, boolean includeRefreshToken, String username, String authenticationId) throws OAuthSystemException { + private AccessToken createAndSaveAccessToken(ClientDetails clientDetails, boolean includeRefreshToken, String username, String authenticationId) + throws OAuthSystemException { + AccessToken accessToken = new AccessToken() .clientId(clientDetails.getClientId()) .username(username) - .tokenId(oAuthIssuer.accessToken()) +// .tokenId(tokenId) .authenticationId(authenticationId) .updateByClientDetails(clientDetails); + String tokenId = oAuthIssuer.accessToken(); + if (needEnhanceJwt()) { + tokenId = jwtTokenEnhancer.enhance(tokenId, username, clientDetails.getClientId(), accessToken.tokenExpiredSeconds()); + } + accessToken.tokenId(tokenId); + if (includeRefreshToken) { accessToken.refreshToken(oAuthIssuer.refreshToken()); } @@ -273,4 +310,15 @@ public class OauthServiceImpl implements OauthService { } + /** + * 判断是否需要增强token 用 jwt + * + * @return true yes + * @since 2.0.0 + */ + private boolean needEnhanceJwt() { + return Constants.JWT.equalsIgnoreCase(this.tokenGeneratorType); + } + + } diff --git a/authz/src/main/java/com/monkeyk/os/service/impl/UserServiceImpl.java b/authz/src/main/java/com/monkeyk/os/service/impl/UserServiceImpl.java index 99e865a441199e89bf973181f7fcea2d2a7068b1..23138dd9632301fa4e66538e04da84da248edcb5 100644 --- a/authz/src/main/java/com/monkeyk/os/service/impl/UserServiceImpl.java +++ b/authz/src/main/java/com/monkeyk/os/service/impl/UserServiceImpl.java @@ -24,6 +24,9 @@ public class UserServiceImpl implements UserService { @Autowired private UsersAuthzRepository usersAuthzRepository; + @Autowired + private UsersFormSaver usersFormSaver; + @Override public UsersOverviewDto loadUsersOverviewDto(String username) { List usersList = usersAuthzRepository.findUsersByUsername(username); @@ -44,7 +47,6 @@ public class UserServiceImpl implements UserService { @Override public String saveUsers(UsersFormDto formDto) { - UsersFormSaver saver = new UsersFormSaver(formDto); - return saver.save(); + return this.usersFormSaver.save(formDto); } } diff --git a/authz/src/main/java/com/monkeyk/os/web/context/BeanContextAware.java b/authz/src/main/java/com/monkeyk/os/web/context/BeanContextAware.java new file mode 100644 index 0000000000000000000000000000000000000000..fc36dac1e1ff9bb306966c9cc104783f74235694 --- /dev/null +++ b/authz/src/main/java/com/monkeyk/os/web/context/BeanContextAware.java @@ -0,0 +1,32 @@ +package com.monkeyk.os.web.context; + +import com.monkeyk.os.domain.shared.BeanProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +import static com.monkeyk.os.domain.oauth.Constants.VERSION; + + +/** + * 2020/7/14 + * + * @author Shengzhao Li + * @since 2.0.0 + */ +@Component +public class BeanContextAware implements ApplicationContextAware { + + private static final Logger LOG = LoggerFactory.getLogger(BeanContextAware.class); + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + BeanProvider.initialize(applicationContext); + if (LOG.isInfoEnabled()) { + LOG.info("Deploy [authz] version: {}", VERSION); + } + } +} diff --git a/authz/src/main/java/com/monkeyk/os/web/context/BeanContextLoaderListener.java b/authz/src/main/java/com/monkeyk/os/web/context/BeanContextLoaderListener.java index c59a2ee82eb056a1545e3455a1220e6856b316f7..7c1af040fce0e1de119fe21d05762c824d3d1c5f 100644 --- a/authz/src/main/java/com/monkeyk/os/web/context/BeanContextLoaderListener.java +++ b/authz/src/main/java/com/monkeyk/os/web/context/BeanContextLoaderListener.java @@ -9,7 +9,9 @@ import javax.servlet.ServletContextEvent; /** * @author Shengzhao Li + * @deprecated Use BeanContextAware.java from 2.0.0 */ +@Deprecated public class BeanContextLoaderListener extends ContextLoaderListener { diff --git a/authz/src/main/java/com/monkeyk/os/web/controller/OauthTokenController.java b/authz/src/main/java/com/monkeyk/os/web/controller/OauthTokenController.java index 210d03c3525f3c48ff5ed7ffbf835844a9b4a69b..0d75d6e31ec77e8ce847602563366defa84289a0 100644 --- a/authz/src/main/java/com/monkeyk/os/web/controller/OauthTokenController.java +++ b/authz/src/main/java/com/monkeyk/os/web/controller/OauthTokenController.java @@ -8,6 +8,7 @@ import org.apache.oltu.oauth2.common.exception.OAuthProblemException; import org.apache.oltu.oauth2.common.exception.OAuthSystemException; import org.apache.oltu.oauth2.common.message.OAuthResponse; import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import javax.servlet.http.HttpServletRequest; @@ -35,9 +36,9 @@ public class OauthTokenController { * * @param request HttpServletRequest * @param response HttpServletResponse - * @throws OAuthSystemException + * @throws OAuthSystemException e */ - @RequestMapping("token") + @PostMapping("token") public void authorize(HttpServletRequest request, HttpServletResponse response) throws OAuthSystemException { try { OAuthTokenxRequest tokenRequest = new OAuthTokenxRequest(request); diff --git a/authz/src/main/java/com/monkeyk/os/web/controller/ShiroController.java b/authz/src/main/java/com/monkeyk/os/web/controller/ShiroController.java index 048273c67f5577d2d20eea148b429b5d85bb9be4..c9cad5bf175522f79a3cf2422b570ff5bb45088e 100644 --- a/authz/src/main/java/com/monkeyk/os/web/controller/ShiroController.java +++ b/authz/src/main/java/com/monkeyk/os/web/controller/ShiroController.java @@ -36,7 +36,7 @@ public class ShiroController { } - /* + /** * Go login page */ @RequestMapping(value = "login", method = RequestMethod.GET) diff --git a/authz/src/main/resources/application.properties b/authz/src/main/resources/application.properties new file mode 100644 index 0000000000000000000000000000000000000000..e81923b1160b0bbc7bfde33fcde12b4cf29e69a3 --- /dev/null +++ b/authz/src/main/resources/application.properties @@ -0,0 +1,32 @@ +# authz properties +# +spring.application.name=AuthZ +# +# +# Support deploy to a servlet-container +spring.jmx.enabled=false +#Override default +spring.main.allow-bean-definition-overriding=true +# +#JDBC +#Connection +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.datasource.url=jdbc:mysql://localhost:3306/oauth2_shiro?autoReconnect=true&autoReconnectForPools=true&useUnicode=true&characterEncoding=utf8&useSSL=false +spring.datasource.username=andaily +spring.datasource.password=andaily +#Datasource properties +spring.datasource.type=com.zaxxer.hikari.HikariDataSource +spring.datasource.hikari.maximum-pool-size=20 +# Logging +# +# +# MVC +spring.thymeleaf.encoding=UTF-8 +spring.thymeleaf.cache=false +# +server.port=8080 +# +# security upgrade config property +# +#authz.store.credentials.alg=SHA-256 +# \ No newline at end of file diff --git a/authz/src/main/resources/authz.properties b/authz/src/main/resources/authz.properties deleted file mode 100644 index ed4fc6aa2a539c2e4f504968aa6571228bc8a986..0000000000000000000000000000000000000000 --- a/authz/src/main/resources/authz.properties +++ /dev/null @@ -1,11 +0,0 @@ - -# ݿ -#JDBC configuration information -jdbc.driverClassName=com.mysql.jdbc.Driver -############ -# localhost -############ -jdbc.url=jdbc:mysql://localhost:3306/oauth2_shiro?autoReconnect=true&autoReconnectForPools=true&useUnicode=true&characterEncoding=utf8 -jdbc.username=andaily -jdbc.password=andaily - diff --git a/authz/src/main/resources/banner.txt b/authz/src/main/resources/banner.txt new file mode 100644 index 0000000000000000000000000000000000000000..8a6aa6ab9b5722fbe072bad4248f6fa11e6ef295 --- /dev/null +++ b/authz/src/main/resources/banner.txt @@ -0,0 +1,10 @@ +${AnsiColor.BRIGHT_GREEN} + ____ _ _____ _ ____ +/ _ \/ \ /\/__ __\/ \ /|/_ \ +| / \|| | || / \ | |_|| / / +| |-||| \_/| | | | | ||/ /_ +\_/ \|\____/ \_/ \_/ \|\____/ + + +AuthZ: ${application.formatted-version} +Spring Boot: ${spring-boot.formatted-version} diff --git a/authz/src/main/resources/logback.xml b/authz/src/main/resources/logback.xml new file mode 100644 index 0000000000000000000000000000000000000000..dbcce6362ddc272e017facdc6b03ed36bc547763 --- /dev/null +++ b/authz/src/main/resources/logback.xml @@ -0,0 +1,47 @@ + + + ${spring.application.name} + + + + + + + + %d{yyyy-MM-dd HH:mm:ss} [%-5level] [%.80c{10}][%L] -%m%n + + + + + + + true + + + logs/%d{yyyy-MM-dd}/authz-%i.log + 10MB + 15 + + + + + %d{yyyy-MM-dd HH:mm:ss} [%-5level] [%.80c{10}][%L] -%m%n + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/authz/src/main/resources/logging.properties b/authz/src/main/resources/logging.properties deleted file mode 100644 index 1d66b3e2b32eb6debe0068af283200d3092ae229..0000000000000000000000000000000000000000 --- a/authz/src/main/resources/logging.properties +++ /dev/null @@ -1,14 +0,0 @@ -handlers = org.apache.juli.FileHandler, java.util.logging.ConsoleHandler - -############################################################ -# Handler specific properties. -# Describes specific configuration info for Handlers. -# The configuration for Tomcat server -############################################################ - -org.apache.juli.FileHandler.level = FINE -org.apache.juli.FileHandler.directory = ${catalina.base}/logs -org.apache.juli.FileHandler.prefix = error-debug. - -java.util.logging.ConsoleHandler.level = FINE -java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter diff --git a/authz/src/main/resources/spring/authz-context.xml b/authz/src/main/resources/spring/authz-context.xml deleted file mode 100644 index e15d9f709f91d25a686bf0bd708dd9bca57a292b..0000000000000000000000000000000000000000 --- a/authz/src/main/resources/spring/authz-context.xml +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - - - - - - - - classpath:authz.properties - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/authz/src/main/resources/spring/authz-security.xml b/authz/src/main/resources/spring/authz-security.xml deleted file mode 100644 index 51e0a24ba274133fb0d69302fd9b301984fae905..0000000000000000000000000000000000000000 --- a/authz/src/main/resources/spring/authz-security.xml +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /favicon.ico = anon - /resources/** = anon - /login = anon - /unauthorized = anon - # OAuth anon - /oauth/** = anon - /users/** = anon - /client_details* = anon - /client_details/** = anon - /logout = logout - # admin role - /admin/** = authc, roles["Admin"] - #user permissions - /user/list = authc, perms["user:list"] - /user/create = authc, perms["user:create"] - # everything else requires authentication: - /** = authc - - - - - - \ No newline at end of file diff --git a/authz/src/main/webapp/resources/bootstrap.min.css b/authz/src/main/resources/static/css/bootstrap.min.css similarity index 100% rename from authz/src/main/webapp/resources/bootstrap.min.css rename to authz/src/main/resources/static/css/bootstrap.min.css diff --git a/authz/src/main/webapp/favicon.ico b/authz/src/main/resources/static/favicon.ico similarity index 100% rename from authz/src/main/webapp/favicon.ico rename to authz/src/main/resources/static/favicon.ico diff --git a/resources/src/main/webapp/static/OS_API-0.2.html b/authz/src/main/resources/static/html/OS_API-0.2.html similarity index 99% rename from resources/src/main/webapp/static/OS_API-0.2.html rename to authz/src/main/resources/static/html/OS_API-0.2.html index bbd9fbd6a39271534007c001e5c5487a92daef12..3b8cfd59dd660c31b6929366b06c58008fe9eeab 100644 --- a/resources/src/main/webapp/static/OS_API-0.2.html +++ b/authz/src/main/resources/static/html/OS_API-0.2.html @@ -4,7 +4,7 @@ - + oauth2-shiro API + + +

+ +
+ 说明: 本文档用于描述oauth2-shiro对外开发的接口(API)使用,分为 authz 与 resources 两个部分, 所有标记 + public + 的API都是公开的, 其他的API则需要获取 + access_token + 后可调用 +
+ +
+ +
+ +
+

[authz]

+ +

获取access_token (grant_type=password) + public +

+ +

使用grant_type=password方式来获取access_token

+ +
    +
  • +

    + 请求URI: /oauth/token POST +

    + +
    + 请求参数说明: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数名参数值必须?备注
    client_id{client_id}
    client_secret{client_secret}
    grant_typepassword固定值
    scope{scope}read or write
    username{username}用户名
    password{password}用户密码
    + 请求示例: +
    http://localhost:8080/oauth/token?client_id=test-client&client_secret=Test@2015$$&grant_type=password&scope=read&username=test&password=Test@2015#
    +
    curl --location 'http://localhost:8080/oauth/token' \
    +--header 'Content-Type: application/x-www-form-urlencoded' \
    +--data-urlencode 'client_id=mobile-client' \
    +--data-urlencode 'client_secret=Mobile@2015$$' \
    +--data-urlencode 'grant_type=password' \
    +--data-urlencode 'username=test' \
    +--data-urlencode 'password=Test@2015#' \
    +--data-urlencode 'scope=read'
    + +
    +
    + + 响应 + +
      +
    • +
      + 正常 [200]
      +
      {
      +    "access_token": "eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiNDllOTQ0N2E2ZGJlMTUyZTBkMmE2YjYzNDRmZmQyNWQiLCJpYXQiOjE2OTU4NjY1NDAsImV4cCI6MTY5NTkwOTc0MCwiYXVkIjoibW9iaWxlLWNsaWVudCJ9.WjB1lvsccXXBJiOeHMuvo-kZmpfpi1YQgU8NuGYmR70",
      +    "refresh_token": "46a2017568aee3875a42f7c2234f4b3d",
      +    "token_type": "Bearer",
      +    "expires_in": 43199
      +}
      +
      +
    • +
    • +
      + 异常 [400]
      +
      {"error":"invalid_grant","error_description":"Bad credentials"}
      +
      +
    • +
    +
  • +
+
+ +
+

[authz]

+ +

获取access_token (grant_type=authorization_code) + public +

+ +

使用grant_type=authorization_code 方式来获取access_token, 需要先获取code

+ +
    +
  • +

    + 请求URI: /oauth/token POST +

    + +
    + 请求参数说明: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数名参数值必须?备注
    client_id{client_id}
    client_secret{client_secret}
    grant_typeauthorization_code固定值
    code{code}
    redirect_uri{redirect_uri}
    + 请求示例: +
    http://localhost:8080/oauth/token?client_id=test-client&client_secret=Test@2015$$&grant_type=authorization_code&code=ac0bd18863b07adfb518cc6e6dfcfcab&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fspring-oauth-client%2Fauthorization_code_callback
    +
    curl --location 'http://localhost:8080/oauth/token' \
    +--header 'Content-Type: application/x-www-form-urlencoded' \
    +--data-urlencode 'client_id=test-client' \
    +--data-urlencode 'client_secret=Test@2015$$' \
    +--data-urlencode 'grant_type=authorization_code' \
    +--data-urlencode 'redirect_uri=http://localhost:7777/spring-oauth-client/authorization_code_callback' \
    +--data-urlencode 'code=52aa9d9cb8e62649e887e745fda94fa7'
    +
    +
    + + 响应 + +
      +
    • +
      + 正常 [200]
      +
      {
      +    "access_token": "eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiY2I2ZmIzYTFmNzA0OGU3NDYxZjcwYWI2OGNkYTk1ZjUiLCJpYXQiOjE2OTU4NjYzODIsImV4cCI6MTY5NTkwOTU4MiwiYXVkIjoidGVzdC1jbGllbnQifQ.NqJe-j7p3UC2gJlBJ-tKB4GrFsW9OR-GyxMfm4LIfwQ",
      +    "refresh_token": "019b043ddcf5994220617b6795c5216a",
      +    "token_type": "Bearer",
      +    "expires_in": 43199
      +}
      +
      +
    • +
    • +
      + 异常 [400]
      +
      {"error":"invalid_grant","error_description":"Invalid code '26964e42c667b5d42f89a1255766630a'"}
      +
      +
    • +
    +
  • +
+
+ +
+

[authz]

+ +

获取access_token (grant_type=token) + public +

+ +

使用grant_type=token 方式来获取access_token, implicit模式; 需要登录

+ +
    +
  • +

    + 请求URI: /oauth/token GET [deprecated] +

    + +
    + 请求参数说明: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数名参数值必须?备注
    client_id{client_id}
    grant_typetoken固定值
    scope{scope}read or write
    redirect_uri{redirect_uri}
    + 请求示例: +
    http://localhost:8080/oauth/authorize?response_type=token&scope=read write&client_id=test-client&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fspring-oauth-client%2Fauthorization_code_callback
    + +
    +
    + + 响应 + +
      +
    • +
      + 正常 [200]
      +
      http://localhost:7777/spring-oauth-client/authorization_code_callback#access_token=eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiZmI2YWM1M2E5YjRlMjFkMjcyMmU4Y2FjMDBjODkyNGUiLCJpYXQiOjE2OTU4Njc3MjMsImV4cCI6MTY5NTkxMDkyMywiYXVkIjoidGVzdC1jbGllbnQifQ.i7WyVE_08DeKeq_SpI-C2sqTaKDXt-wKck1L_L_aW98&token_type=Bearer&expires_in=43199
      +
      +
    • +
    +

    通过 redirect_uri的 URL hash 传递access_token信息

    +
  • +
+
+ +
+

[authz]

+ +

获取access_token (grant_type=client_credentials) + public +

+ +

使用grant_type=client_credentials 方式来获取access_token, 不需要username, password, 不支持 + refresh_token

+ +
    +
  • +

    + 请求URI: /oauth/token POST +

    + +
    + 请求参数说明: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数名参数值必须?备注
    client_id{client_id}
    client_secret{client_secret}
    grant_typeclient_credentials固定值
    scope{scope}read or write
    + 请求示例: +
    http://localhost:8080/oauth/token?client_id=test-client&client_secret=Test@2015$$&grant_type=client_credentials&scope=read
    + +
    curl --location 'http://localhost:8080/oauth/token' \
    +--header 'Content-Type: application/x-www-form-urlencoded' \
    +--data-urlencode 'client_id=test-client' \
    +--data-urlencode 'client_secret=Test@2015$$' \
    +--data-urlencode 'grant_type=client_credentials' \
    +--data-urlencode 'scope=read'
    +
    +
    + + 响应 + +
      +
    • +
      + 正常 [200]
      +
      {
      +    "access_token": "eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0LWNsaWVudCIsImp0aSI6IjlkNTZhMjFhYzNhZGMzMWQyYzRjZDJlOWEyNTNkY2RmIiwiaWF0IjoxNjk1ODY2NjA5LCJleHAiOjE2OTU5MDk4MDksImF1ZCI6InRlc3QtY2xpZW50In0.brapFTd_HiPfrlKZWOK9MXOFKrDRD7v2dqXnGU7nkjI",
      +    "token_type": "Bearer",
      +    "expires_in": 43199
      +}
      +
      +
    • +
    • +
      + 异常 [401]
      +
      {"error":"invalid_client","error_description":"Invalid client_id'test-xxx'"}
      +
      +
    • +
    +
  • +
+
+ +
+

[authz]

+ +

刷新access_token (grant_type=refresh_token) + public +

+ +

用于在access_token要过期时换取新的access_token (grant_type需要有refresh_token)

+ +
    +
  • +

    + 请求URI: /oauth/token POST +

    + +
    + 请求参数说明: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数名参数值必须?备注
    client_id{client_id}
    client_secret{client_secret}
    grant_typerefresh_token固定值
    refresh_token{refresh_token}
    + 请求示例: +
    http://localhost:8080/oauth/token?client_id=test-client&client_secret=Test@2015$$&grant_type=refresh_token&refresh_token=b36f4978a1724aa8af8960f58abe3ba1
    + +
    curl --location 'http://localhost:8080/oauth/token' \
    +--header 'Content-Type: application/x-www-form-urlencoded' \
    +--data-urlencode 'client_id=test-client' \
    +--data-urlencode 'client_secret=Test@2015$$' \
    +--data-urlencode 'grant_type=refresh_token' \
    +--data-urlencode 'refresh_token=8c46797a0101800626270ce6579c84fa'
    +
    +
    + + 响应 + +
      +
    • +
      + 正常 [200]
      +
      {
      +    "access_token": "eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiZTUyYTExZGU4Y2EwNGUwOTI1Y2RhNDMxNDYwN2NmZGUiLCJpYXQiOjE2OTU4NjY2NzksImV4cCI6MTY5NTkwOTg3OSwiYXVkIjoidGVzdC1jbGllbnQifQ.qLkvnAartpxkiFfeMwnzrK61ihJtXAu6ml5tFU8O-NU",
      +    "refresh_token": "bb277d6ba38bbf5d6facae92eb29e286",
      +    "token_type": "Bearer",
      +    "expires_in": 43199
      +}
      +
      +
    • +
    • +
      + 异常 [400]
      +
      {"error":"invalid_grant","error_description":"Invalid refresh_token: 8e91a56f53857688a3ffd8c7cfd311cfss"}
      +
      +
    • +
    +
  • +
+
+ +
+ +
+

[resources]

+ +

获取当前系统时间(resource-id: mobile-resource)

+ +

获取当前系统时间, 需要access_token的 resource-id 为 mobile-resource 才能访问

+ +
    +
  • +

    + 请求URI: /mobile/system_time GET +

    + +
    + 请求参数说明: + + + + + + + + + + + + + + +
    参数名参数值必须?备注
    + 请求示例: +
    http://localhost:8083/mobile/system_time?access_token=eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiNDllOT...
    + +
    curl --location 'http://localhost:8083/mobile/system_time' \
    +--header 'Authorization: Bearer eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiNDllOTQ0N2E2ZGJlMTUyZTBkMmE2YjYzNDRmZmQyNWQiLCJpYXQiOjE2OTU4NjY1NDAsImV4cCI6MTY5NTkwOTc0MCwiYXVkIjoibW9iaWxlLWNsaWVudCJ9.WjB1lvsccXXBJiOeHMuvo-kZmpfpi1YQgU8NuGYmR70'
    +
    + +
    +
    + + 响应 + +
      +
    • +
      + 正常 [200]
      +
      {
      +    "time": 1695628213913
      +}
      +
      +
    • +
    • +
      + 异常 [401]
      +
      {"error":"invalid_token","error_description":"Invalid access_token: 95c3afd44c5d87301dc3034b20b3fc75s"}
      +
      +
    • +
    +
  • +
+
+ +
+

[resources]

+ +

获取当前用户信息 (resource-id: os-resource; Role: User)

+ +

使用access_token获取用户信息, 需要access_token的 resource-id 为 os-resource 且用户Role包含 User + 才能访问

+ +
    +
  • +

    + 请求URI: /rs/username GET +

    + +
    + 请求参数说明: + + + + + + + + + + + + + + +
    参数名参数值必须?备注
    + 请求示例: +
    http://localhost:8083/rs/username?access_token=eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIi...
    + +
    curl --location 'http://localhost:8083/rs/username' \
    +--header 'Authorization: Bearer eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiZTUyYTExZGU4Y2EwNGUwOTI1Y2RhNDMxNDYwN2NmZGUiLCJpYXQiOjE2OTU4NjY2NzksImV4cCI6MTY5NTkwOTg3OSwiYXVkIjoidGVzdC1jbGllbnQifQ.qLkvnAartpxkiFfeMwnzrK61ihJtXAu6ml5tFU8O-NU'
    +
    + +
    +
    + + 响应 + +
      +
    • +
      + 正常 [200]
      +
      {
      +    "clientId": "test-client",
      +    "username": "test"
      +}
      +
      +
    • +
    • +
      + 异常 [401]
      +
      {"error":"invalid_token","error_description":"Invalid client by token: 95c3afd44c5d87301dc3034b20b3fc75"}
      +
      +
    • +
    +
  • +
+
+ +
+
+ + +
+
+
+
+ © 2015-2023 oauth2-shiro +
+
+
+ + \ No newline at end of file diff --git a/authz/src/main/webapp/resources/oauth_test.html b/authz/src/main/resources/static/html/oauth_test.html similarity index 93% rename from authz/src/main/webapp/resources/oauth_test.html rename to authz/src/main/resources/static/html/oauth_test.html index de93929325436c69ee307ef8b05dbf52762437d5..6b5773d7b55f80347273d17a262a30ceeb160c91 100644 --- a/authz/src/main/webapp/resources/oauth_test.html +++ b/authz/src/main/resources/static/html/oauth_test.html @@ -3,13 +3,13 @@ - + OAuth2-Shiro Test
- Home + Home
@@ -20,7 +20,7 @@
-
@@ -28,7 +28,7 @@
-
@@ -77,7 +77,7 @@
-
@@ -85,7 +85,7 @@
-
@@ -118,7 +118,7 @@
+ value="Test@2015#" required="true"/>
@@ -143,7 +143,7 @@
-
@@ -151,7 +151,7 @@
-
@@ -190,7 +190,7 @@
-
@@ -198,7 +198,7 @@
-
@@ -242,7 +242,7 @@

- © oauth2-shiro + © 2015-2023 oauth2-shiro
diff --git a/authz/src/main/webapp/resources/angular.min.js b/authz/src/main/resources/static/js/angular.min.js similarity index 100% rename from authz/src/main/webapp/resources/angular.min.js rename to authz/src/main/resources/static/js/angular.min.js diff --git a/authz/src/main/resources/templates/fragments/main.html b/authz/src/main/resources/templates/fragments/main.html new file mode 100644 index 0000000000000000000000000000000000000000..183ad85c5d7e58ce164b53fef99c181f32a5c9bd --- /dev/null +++ b/authz/src/main/resources/templates/fragments/main.html @@ -0,0 +1,32 @@ + + + + + + Fragments + +
+ + +
+ + + + + + + + \ No newline at end of file diff --git a/authz/src/main/resources/templates/index.html b/authz/src/main/resources/templates/index.html new file mode 100644 index 0000000000000000000000000000000000000000..89b96c65b36945b2a305d5ac616d1f0739361711 --- /dev/null +++ b/authz/src/main/resources/templates/index.html @@ -0,0 +1,48 @@ + + + + + + + + + Home . OAuth2-Shiro[authz] + + + + +
+

OAuth2-Shiro is work!

+ Logout +
+ Welcome: + +
+
+ 测试OAuth + +

+ oauth_test +

+
+
+
+ 菜单 + +

根据不同的Role会显示不同的菜单 (Shiro权限控制)

+ +
+ +
+
+ + \ No newline at end of file diff --git a/authz/src/main/resources/templates/login.html b/authz/src/main/resources/templates/login.html new file mode 100644 index 0000000000000000000000000000000000000000..ee911c0443b1211d60160090111fad80ec67aaab --- /dev/null +++ b/authz/src/main/resources/templates/login.html @@ -0,0 +1,125 @@ + + + + + + + + + Login . OAuth2-Shiro[authz] + + + + +
+ + +
+

[authz]模块用于管理client_details, user,以及获取access_token去访问 [resources] 模块的资源.

+ + 操作说明 +
    +
  1. +

    登录系统,使用初始的账号 test/Test@2015# 或去 Users 先创建用户, 这用于测试Shiro安全是否工作

    + +
    +
    +
    +
    + + +
    + (test) +
    +
    +
    + + +
    + (Test@2015#) +
    +
    +
    +
    +   + +
    +
    +
    +
    +
    +
    +
  2. +
  3. +

    + 在开始OAuth2之前, 建议先理解OAuth2支持的5类grant_type, 请访问 https://andaily.com/blog/?p=103, + 或直接去看看OAuth2协议 http://oauth.net/2/ +

    +
  4. +
  5. +

    + 管理client_details, 在初始时创建了默认的管理client_details + test + 与 + mobile + (见initial-db.ddl文件), 你可以去 client_details + 创建新的client_details来测试.
    ----- client_details是OAuth2中一个核心的组件 +

    +
  6. +
  7. +

    + 查看 oauth_test.txt + 文件并进行OAuth2的流程测试; 也可下载 + spring-oauth-client + 项目来测试OAuth2的流程 +

    +
  8. +
+
+ +
+

菜单

+ +
    +
  • +

    + Users -- 管理User +

    +
  • +
  • +

    + client_details -- 管理client_details +

    +
  • +
  • +

    + oauth_test.txt + -- 测试OAuth2的参考URL文件 +

    +
  • +
  • +

    + API + -- oauth2-shiro提供的API文档 +

    +
  • +
  • +

    + oauth_test.html -- + 一个用于测试各类grant_type的HTML页面 +

    +
  • +
+ +
+ +
+
+ + \ No newline at end of file diff --git a/authz/src/main/resources/templates/oauth/client_details.html b/authz/src/main/resources/templates/oauth/client_details.html new file mode 100644 index 0000000000000000000000000000000000000000..1029fa58f239b771eccdc8df6d44cd314ea2ded1 --- /dev/null +++ b/authz/src/main/resources/templates/oauth/client_details.html @@ -0,0 +1,91 @@ + + + + + + + + + Client Details . OAuth2-Shiro[authz] + + + + + +
+ Home + +

Client Details

+ + +
+
+ +
+ +  Total: [[${listDto.size}]] +
+
+ +
+
    + +
  • +

    Empty result

    +
  • + + +
  • +
    + + test + + Archived +
    +

    + [[${cli.clientName}]] + [[${cli.grantTypes}]] +

    + +
    + client_id:   + client_secret:   +
    + grant_types:   + resource_ids:   +
    + scope:   + redirect_uri:   +
    + roles:   + access_token_validity:   + refresh_token_validity:   +
    + client_uri:   + client_icon_uri:   +
    + create_time:   + archived:   + trusted:   + description:   +
    +
  • + + +
+

+ 每一个item对应oauth_client_details表中的一条数据; 共[[${listDto.size}]]条数据. +

+
+ +
+
+ + \ No newline at end of file diff --git a/authz/src/main/resources/templates/oauth/client_details_plus.html b/authz/src/main/resources/templates/oauth/client_details_plus.html new file mode 100644 index 0000000000000000000000000000000000000000..65edf29254eac9ec77c6eb5f22e5f5b8c76bf850 --- /dev/null +++ b/authz/src/main/resources/templates/oauth/client_details_plus.html @@ -0,0 +1,251 @@ + + + + + + + + + Add Client . OAuth2-Shiro[authz] + + + + + +
+ Home + +

Add Client

+ +
+

+ 这里列出了一个 client_details 需要的所有属性进行添加, 但在实际使用场景中, 许多属性是由系统处理的, 不需要用户关心. +

+ +
+ +
+
+ + +
+ + +

client_id必须输入,且必须唯一,长度至少5位; 在实际应用中的另一个名称叫appKey,与client_id是同一个概念.

+
+
+
+ + +
+ + +

client_secret必须输入,且长度至少8位; + 在实际应用中的另一个名称叫appSecret,与client_secret是同一个概念.

+
+
+
+ + +
+ + +

resourceIds, 用于定义一组资源的Id, 在[resources]模块中使用, + 可在[resources]模块的rs-security.xml查看定义的resourceId(每一个 OAuth2Filter 代表一个资源)

+
+
+ +
+ + +
+ + +

scope必须选择

+
+
+ +
+ + +
+ + + + + + +

至少勾选一项grant_type(s), 且不能只单独勾选refresh_token, 若需更多帮助请访问 https://andaily.com/blog/?p=103

+
+
+ +
+ + +
+ + +

grant_type包括authorization_code,implicit, + 则必须输入redirect_uri

+
+
+ +
+ + +
+ + +

指定客户端所拥有的Shiro Role,可选; 尚未使用

+
+
+ +
+ + +
+ + +

设定客户端的access_token的有效时间值(单位:秒),可选, 若不设定值则使用默认的有效时间值(60 * 60 * 12, 12小时); + 若设定则必须是大于0的整数值

+
+
+ +
+ + +
+ + +

设定客户端的refresh_token的有效时间值(单位:秒),可选, 若不设定值则使用默认的有效时间值(60 * 60 * 24 * 30, + 30天); + 若设定则必须是大于0的整数值

+
+
+ +
+ + +
+ + +

给Client起一个有意义的名称

+
+
+ +
+ + +
+ + +

该Client的URI, 可选

+
+
+
+ + +
+ + +

该Client的Icon URI, 可选

+
+
+ +
+ + +
+ + +

Client的描述, 可选

+
+
+ + +
+ + +
+ + + +

该属性是扩展的, + 只适用于grant_type(s)包括authorization_code的情况,当用户登录成功后,若选No,则会跳转到让用户Approve的页面让用户同意授权, + 若选Yes,则在登录后不需要再让用户Approve同意授权(因为是受信任的)

+
+
+ + +
+
+
+ +
+
+ +
+
+
+ + 取消 +
+
+
+
+ + +
+
+ + \ No newline at end of file diff --git a/authz/src/main/resources/templates/oauth/oauth_approval.html b/authz/src/main/resources/templates/oauth/oauth_approval.html new file mode 100644 index 0000000000000000000000000000000000000000..3084c704f46489229682312866a39c8e293daa6c --- /dev/null +++ b/authz/src/main/resources/templates/oauth/oauth_approval.html @@ -0,0 +1,48 @@ + + + + + + + + + OAuth Approval . OAuth2-Shiro[authz] + + + + + +
+

OAuth Approval

+ +

Do you authorize '' to access your protected resources?

+ +
+ + + + + + + + + +
+ +
+ + + + + + + + + +
+ +
+
+ + \ No newline at end of file diff --git a/authz/src/main/resources/templates/oauth/oauth_login.html b/authz/src/main/resources/templates/oauth/oauth_login.html new file mode 100644 index 0000000000000000000000000000000000000000..a4a875b32e81e0e3efa7bc37fb565719c235803f --- /dev/null +++ b/authz/src/main/resources/templates/oauth/oauth_login.html @@ -0,0 +1,89 @@ + + + + + + + + + OAuth Login . OAuth2-Shiro[authz] + + + + + +
+ +
+

OAuth Login

+ +

+ 登录使用的 client_id: ''. +

+ +
+
+
+ + + + + + + +
+ + +
+ +
+
+
+ + +
+ +
+
+
+
+ +   + Login failed +
+
+
+
+
+
+ +
+
+ +

可以使用下面的初始账号登录或使用你自行添加的账号:

+ + + + + + + + + + + + + + + + + +
usernamepasswordgrant_typesroles
testTest@2015#authorization_code,password,refresh_token,client_credentialsUser(id=22)
+
+ +
+
+ + \ No newline at end of file diff --git a/authz/src/main/webapp/WEB-INF/jsp/oauth/test_client.jsp b/authz/src/main/resources/templates/oauth/test_client.html similarity index 36% rename from authz/src/main/webapp/WEB-INF/jsp/oauth/test_client.jsp rename to authz/src/main/resources/templates/oauth/test_client.html index d2e0e23b160696f1344ecfbab1352412b9314a38..e57942f789c68f04968bf20d2495b7a09a39e15f 100644 --- a/authz/src/main/webapp/WEB-INF/jsp/oauth/test_client.jsp +++ b/authz/src/main/resources/templates/oauth/test_client.html @@ -1,48 +1,56 @@ -<%-- - * - * @author Shengzhao Li ---%> - -<%@ page contentType="text/html;charset=UTF-8" language="java" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> - - + + - Test [${clientDetailsDto.clientId}] + + + + - + Test [[${clientDetailsDto.clientId}]] . OAuth2-Shiro[authz] + + + -
- Home +
+
+ Home -

Test [${clientDetailsDto.clientId}]

+

Test [[${clientDetailsDto.clientId}]]

-

- 针对不同的grant_type提供不同的测试URL, - 完整的OAuth测试请访问spring-oauth-client项目. -

+

+ 针对不同的grant_type提供不同的测试URL, + 完整的OAuth测试请访问spring-oauth-client项目. +

-
- -
+
+ +
Test [authorization_code]

输入每一步必要的信息后点击其下面的链接地址.

  1. -

    +

    从 oauth-shiro 获取 'code'
    redirect_uri:
    - - /oauth/authorize?client_id={{clientId}}&redirect_uri={{redirectUri}}&response_type=code&scope={{scope}}&state=123456 +
    + + + + + + + +
    GET -

    +
  2. 用 'code' 换取 'access_token' @@ -51,8 +59,12 @@ required="required"/>
    -
    + + + + + + @@ -62,10 +74,10 @@
- + - -
+ +
Test [password]

输入username, password 后点击链接地址.

@@ -75,43 +87,59 @@
- + + + + + + + POST +

注意:password模式在OAuth2.1中已不推荐使用(安全系数差)

- + - -
+ +
Test [implicit]

输入redirect_uri 后点击链接地址. 获取access_token后注意查看redirect_uri的hash部分(#号后边部分)

redirect_uri: -

- /oauth/authorize?client_id={{clientId}}&response_type=token&scope={{scope}}&redirect_uri={{implicitRedirectUri}} +

+
+ + + + + +
GET -

+
- + - -
+ +
Test [client_credentials]

点击链接地址即可测试

-
+ + + + + @@ -119,10 +147,10 @@
- + - -
+ +
Test [refresh_token]

输入refresh_token 后点击链接地址.

@@ -130,8 +158,11 @@
-
+ + + + + @@ -139,35 +170,46 @@
- + -
- Back +
+ Back +
-
- + + +
+
\ No newline at end of file diff --git a/authz/src/main/resources/templates/unauthorized.html b/authz/src/main/resources/templates/unauthorized.html new file mode 100644 index 0000000000000000000000000000000000000000..5d2493d76aecaefb8c01024de5d7414e6a1648ba --- /dev/null +++ b/authz/src/main/resources/templates/unauthorized.html @@ -0,0 +1,22 @@ + + + + + + + + + Unauthorized . OAuth2-Shiro[authz] + + + + +
+

unauthorized

+ Home + Back + +
+
+ + \ No newline at end of file diff --git a/authz/src/main/resources/templates/users/user_plus.html b/authz/src/main/resources/templates/users/user_plus.html new file mode 100644 index 0000000000000000000000000000000000000000..f3d9369b78495af3bdf83fe2a5928649dd9766cf --- /dev/null +++ b/authz/src/main/resources/templates/users/user_plus.html @@ -0,0 +1,83 @@ + + + + + + + + + Add User . OAuth2-Shiro[authz] + + + + +
+ Home + +

Add User

+ +
+
+ + +
+ + +

Username, unique.

+
+
+
+ + +
+ + +

Password should include upper and lower case letters, numbers, and special characters, with a minimum length of 10 characters, required.

+
+
+
+ + +
+ +
+ + + + +
+ + +

Select Roles

+
+
+ + +
+
+
+ +
+
+ + +
+
+
+ + Cancel +
+
+
+ + +
+
+ + \ No newline at end of file diff --git a/authz/src/main/resources/templates/users/users_overview.html b/authz/src/main/resources/templates/users/users_overview.html new file mode 100644 index 0000000000000000000000000000000000000000..f8f16592a4902e4ff6c8f17fecbb5af47074d086 --- /dev/null +++ b/authz/src/main/resources/templates/users/users_overview.html @@ -0,0 +1,58 @@ + + + + + + + + + Users . OAuth2-Shiro[authz] + + + + +
+ Home + +

Users

+ +
+ 说明: Users管理正常需要有管理权限(如Admin权限)的账号登录后才能操作,此处未登录就能查看与添加是方便演示功能。 +
+
+ Add User +
+
+
+ +
+ +  Total: [[${overviewDto.usersSize}]] +
+
+ + + + + + + + + + + + + + + +
UsernameRoles[permission]CreateTime
[[${user.username}]] + + [[${r.roleName}]] [[${r.permissions}]] , + + [[${user.createTime}]]
+ +
+
+ + \ No newline at end of file diff --git a/authz/src/main/webapp/WEB-INF/decorators.xml b/authz/src/main/webapp/WEB-INF/decorators.xml deleted file mode 100644 index 1fa1456cf4bf8cf7d6b89da5ecb94fc648b30ff4..0000000000000000000000000000000000000000 --- a/authz/src/main/webapp/WEB-INF/decorators.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - /* - - - - - /resources/** - - - \ No newline at end of file diff --git a/authz/src/main/webapp/WEB-INF/jsp/comm-header.jsp b/authz/src/main/webapp/WEB-INF/jsp/comm-header.jsp deleted file mode 100644 index 0619161f3fc2397c4c1800a092e48d7a3965455a..0000000000000000000000000000000000000000 --- a/authz/src/main/webapp/WEB-INF/jsp/comm-header.jsp +++ /dev/null @@ -1,8 +0,0 @@ -<%--<%@ page session="false" %>--%> - -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> -<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> -<%@ taglib prefix="fun" uri="http://java.sun.com/jsp/jstl/functions" %> -<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> -<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> -<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %> \ No newline at end of file diff --git a/authz/src/main/webapp/WEB-INF/jsp/decorators/main.jsp b/authz/src/main/webapp/WEB-INF/jsp/decorators/main.jsp deleted file mode 100644 index 085a1265a4f602c5445d33a03da02ea603f11bcf..0000000000000000000000000000000000000000 --- a/authz/src/main/webapp/WEB-INF/jsp/decorators/main.jsp +++ /dev/null @@ -1,43 +0,0 @@ -<%-- - * - * @author Shengzhao Li ---%> - -<%@ page contentType="text/html;charset=UTF-8" language="java" %> -<%@ taglib uri="http://www.opensymphony.com/sitemesh/decorator" prefix="decorator" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> - - - - - - - - - - - <decorator:title default=""/> . OAuth2-Shiro[authz] - - - - - - -
- - -
-
-
-
- © 2015-2021 oauth2-shiro -
-
-
-
- - \ No newline at end of file diff --git a/authz/src/main/webapp/WEB-INF/jsp/index.jsp b/authz/src/main/webapp/WEB-INF/jsp/index.jsp deleted file mode 100644 index 0124cfb06f380f59b6eea78bac330e72a31c3f2f..0000000000000000000000000000000000000000 --- a/authz/src/main/webapp/WEB-INF/jsp/index.jsp +++ /dev/null @@ -1,47 +0,0 @@ -<%-- - * - * @author Shengzhao Li ---%> - -<%@ page contentType="text/html;charset=UTF-8" language="java" %> -<%@ include file="comm-header.jsp" %> - - - - Home - - -

Oauth2-Shiro is work!

-Logout -
-Welcome: -
-
- 测试OAuth - -

- oauth_test -

-
-
-
- 菜单 - -

根据不同的Role会显示不同的菜单 (Shiro权限控制)

- -
- - \ No newline at end of file diff --git a/authz/src/main/webapp/WEB-INF/jsp/login.jsp b/authz/src/main/webapp/WEB-INF/jsp/login.jsp deleted file mode 100644 index 4cd4ae624d180bd09ef9168247b01a3fa0006bd6..0000000000000000000000000000000000000000 --- a/authz/src/main/webapp/WEB-INF/jsp/login.jsp +++ /dev/null @@ -1,135 +0,0 @@ -<%-- - * Copyright (c) 2013 Andaily Information Technology Co. Ltd - * www.andaily.com - * All rights reserved. - * - * This software is the confidential and proprietary information of - * Andaily Information Technology Co. Ltd ("Confidential Information"). - * You shall not disclose such Confidential Information and shall use - * it only in accordance with the terms of the license agreement you - * entered into with Andaily Information Technology Co. Ltd. ---%> -<%-- - * - * @author Shengzhao Li ---%> -<%@ page contentType="text/html;charset=UTF-8" language="java" %> -<%@ include file="comm-header.jsp" %> - - - - - - - Login - - - - - -
-

[authz]模块用于管理client_details, user,以及获取access_token去访问 [resources] 模块的资源.

- - 操作说明 -
    -
  1. -

    登录系统,使用初始的账号 test/test 或去 Users 先创建用户, 这用于测试Shiro安全是否工作

    - -
    -
    - -
    - - -
    - (test) -
    -
    -
    - - -
    - (test) -
    -
    -
    -
    -   - -
    -
    -
    -
    -
    -
    -
  2. -
  3. -

    - 在开始OAuth2之前, 建议先理解OAuth2支持的5类grant_type, 请访问 https://andaily.com/blog/?p=103, - 或直接去看看OAuth2协议 http://oauth.net/2/ -

    -
  4. -
  5. -

    - 管理client_details, 在初始时创建了默认的管理client_details - test - 与 - mobile - (见initial-db.ddl文件), 你可以去 client_details - 创建新的client_details来测试.
    ----- client_details是OAuth2中一个核心的组件 -

    -
  6. -
  7. -

    - 查看 oauth_test.txt - 文件并进行OAuth2的流程测试; 也可下载 - spring-oauth-client - 项目来测试OAuth2的流程 -

    -
  8. -
-
- -
-

菜单

- -
    -
  • -

    - Users -- 管理User -

    -
  • -
  • -

    - client_details -- 管理client_details -

    -
  • -
  • -

    - oauth_test.txt - -- 测试OAuth2的参考URL文件 -

    -
  • -
  • -

    - API - -- oauth2-shiro提供的API文档 -

    -
  • -
  • -

    - oauth_test.html -- - 一个用于测试各类grant_type的HTML页面 -

    -
  • -
- -
- - - \ No newline at end of file diff --git a/authz/src/main/webapp/WEB-INF/jsp/oauth/client_details.jsp b/authz/src/main/webapp/WEB-INF/jsp/oauth/client_details.jsp deleted file mode 100644 index f9025c13d25b2cc9f2517e896710dd45e66fb16b..0000000000000000000000000000000000000000 --- a/authz/src/main/webapp/WEB-INF/jsp/oauth/client_details.jsp +++ /dev/null @@ -1,86 +0,0 @@ -<%-- - * - * @author Shengzhao Li ---%> -<%@ page contentType="text/html;charset=UTF-8" language="java" %> -<%@ include file="../comm-header.jsp" %> - - - - Client Details - - - -Home - -

Client Details

- - -
-
- -
- -  Total: ${listDto.size} -
-
- -
-
    - -
  • -

    Empty result

    -
  • -
    - -
  • -
    - - test - - Archived -
    -

    - ${cli.clientName} - ${cli.grantTypes} -

    - -
    - client_id: ${cli.clientId}  - client_secret: ${cli.clientSecret}  -
    - grant_types: ${cli.grantTypes}  - resource_ids: ${cli.resourceIds}  -
    - scope: ${cli.scope}  - redirect_uri: ${cli.redirectUri}  -
    - roles: ${cli.roles}  - access_token_validity: ${cli.accessTokenValidity}  - refresh_token_validity: ${cli.refreshTokenValidity}  -
    - client_uri: ${cli.clientUri}  - client_icon_uri: ${cli.clientIconUri}  -
    - create_time: ${cli.createTime}  - archived: ${cli.archived}  - trusted: ${cli.trusted}  - description: ${cli.description}  -
    -
  • -
    - -
-

- 每一个item对应oauth_client_details表中的一条数据; 共${listDto.size}条数据. -

-
- - diff --git a/authz/src/main/webapp/WEB-INF/jsp/oauth/client_details_plus.jsp b/authz/src/main/webapp/WEB-INF/jsp/oauth/client_details_plus.jsp deleted file mode 100644 index edf5501c737ac6c5ec39068a1fa925515a9df4e1..0000000000000000000000000000000000000000 --- a/authz/src/main/webapp/WEB-INF/jsp/oauth/client_details_plus.jsp +++ /dev/null @@ -1,245 +0,0 @@ -<%-- - * - * @author Shengzhao Li ---%> -<%@ page contentType="text/html;charset=UTF-8" language="java" %> -<%@ include file="../comm-header.jsp" %> - - - - Add Client - - - -Home - -

Add Client

- -
-

- 这里列出了一个 client_details 需要的所有属性进行添加, 但在实际使用场景中, 许多属性是由系统处理的, 不需要用户关心. -

- -
- -
- - -
- - -

client_id必须输入,且必须唯一,长度至少5位; 在实际应用中的另一个名称叫appKey,与client_id是同一个概念.

-
-
-
- - -
- - -

client_secret必须输入,且长度至少8位; 在实际应用中的另一个名称叫appSecret,与client_secret是同一个概念.

-
-
-
- - -
- - os-resource - mobile-resource - - -

resourceIds, 用于定义一组资源的Id, 在[resources]模块中使用, - 可在[resources]模块的rs-security.xml查看定义的resourceId(每一个 OAuth2Filter 代表一个资源)

-
-
- -
- - -
- - read - write - - -

scope必须选择

-
-
- -
- - -
- - - - - - -

至少勾选一项grant_type(s), 且不能只单独勾选refresh_token, 若需更多帮助请访问 https://andaily.com/blog/?p=103

-
-
- -
- - -
- - -

grant_type包括authorization_code,implicit, - 则必须输入redirect_uri

-
-
- -
- - -
- - - - - -

指定客户端所拥有的Shiro Role,可选; 尚未使用

-
-
- -
- - -
- - -

设定客户端的access_token的有效时间值(单位:秒),可选, 若不设定值则使用默认的有效时间值(60 * 60 * 12, 12小时); - 若设定则必须是大于0的整数值

-
-
- -
- - -
- - -

设定客户端的refresh_token的有效时间值(单位:秒),可选, 若不设定值则使用默认的有效时间值(60 * 60 * 24 * 30, - 30天); - 若设定则必须是大于0的整数值

-
-
- -
- - -
- - -

给Client起一个名称

-
-
- -
- - -
- - -

该Client的URI, 可选

-
-
-
- - -
- - -

该Client的Icon URI, 可选

-
-
- -
- - -
- - -

Client的描述, 可选

-
-
- - -
- - -
- - - -

该属性是扩展的, - 只适用于grant_type(s)包括authorization_code的情况,当用户登录成功后,若选No,则会跳转到让用户Approve的页面让用户同意授权, - 若选Yes,则在登录后不需要再让用户Approve同意授权(因为是受信任的)

-
-
-
- - -
-
-
- -
-
- - -
-
-
- - 取消 -
-
- -
- - - - diff --git a/authz/src/main/webapp/WEB-INF/jsp/oauth/oauth_approval.jsp b/authz/src/main/webapp/WEB-INF/jsp/oauth/oauth_approval.jsp deleted file mode 100644 index a19edc842b498d0f5e1422c6d9b8fd9706cedae3..0000000000000000000000000000000000000000 --- a/authz/src/main/webapp/WEB-INF/jsp/oauth/oauth_approval.jsp +++ /dev/null @@ -1,42 +0,0 @@ -<%-- - * - * @author Shengzhao Li ---%> - -<%@ page contentType="text/html;charset=UTF-8" language="java" %> - - - - - Oauth Approval - -

OAuth Approval

- -

Do you authorize '${param.client_id}' to access your protected resources?

- -
- - - - - - - - - -
- -
- - - - - - - - - -
- - \ No newline at end of file diff --git a/authz/src/main/webapp/WEB-INF/jsp/oauth/oauth_login.jsp b/authz/src/main/webapp/WEB-INF/jsp/oauth/oauth_login.jsp deleted file mode 100644 index 1e7b594244afcc528623871e2487b82748311c9e..0000000000000000000000000000000000000000 --- a/authz/src/main/webapp/WEB-INF/jsp/oauth/oauth_login.jsp +++ /dev/null @@ -1,110 +0,0 @@ -<%-- - * Copyright (c) 2013 Andaily Information Technology Co. Ltd - * www.andaily.com - * All rights reserved. - * - * This software is the confidential and proprietary information of - * Andaily Information Technology Co. Ltd ("Confidential Information"). - * You shall not disclose such Confidential Information and shall use - * it only in accordance with the terms of the license agreement you - * entered into with Andaily Information Technology Co. Ltd. ---%> -<%-- - * - * @author Shengzhao Li ---%> -<%@ page contentType="text/html;charset=UTF-8" language="java" %> -<%@ include file="../comm-header.jsp" %> - - - - Oauth Login - - - -
-

Oauth Login

- -

- 登录使用的 client_id: '${param.client_id}'. -

- -
-
-
- - - - - - - -
- - -
- -
-
-
- - -
- -
-
-
-
- -   - Login failed -
-
-
-
-
- <%--
--%> - <%----%> - <%----%> - <%----%> - <%----%> - <%----%> - <%----%> - - <%--Username: --%> - <%--

--%> - <%--Password: --%> - <%--

--%> - <%----%> - <%--
--%> - <%--Login failed--%> - <%--
--%> -
- -
-
- -

可以使用下面的账号登录或使用你自行添加的账号:

- - - - - - - - - - - - - - - - - -
usernamepasswordgrant_typesroles
testtestauthorization_code,password,refresh_token,client_credentialsUser(id=22)
-
- - \ No newline at end of file diff --git a/authz/src/main/webapp/WEB-INF/jsp/unauthorized.jsp b/authz/src/main/webapp/WEB-INF/jsp/unauthorized.jsp deleted file mode 100644 index 8cc08b1dfa1477b7e7376bf7779cdf26e784a6af..0000000000000000000000000000000000000000 --- a/authz/src/main/webapp/WEB-INF/jsp/unauthorized.jsp +++ /dev/null @@ -1,27 +0,0 @@ -<%-- - * Copyright (c) 2013 Andaily Information Technology Co. Ltd - * www.andaily.com - * All rights reserved. - * - * This software is the confidential and proprietary information of - * Andaily Information Technology Co. Ltd ("Confidential Information"). - * You shall not disclose such Confidential Information and shall use - * it only in accordance with the terms of the license agreement you - * entered into with Andaily Information Technology Co. Ltd. ---%> -<%-- - * - * @author Shengzhao Li ---%> -<%@ page contentType="text/html;charset=UTF-8" language="java" %> - - - - UnAuthorized - - -

unauthorized

-Home -Back - - \ No newline at end of file diff --git a/authz/src/main/webapp/WEB-INF/jsp/users/user_plus.jsp b/authz/src/main/webapp/WEB-INF/jsp/users/user_plus.jsp deleted file mode 100644 index 4ce5d2334c66dbe4c15c4a8524d1a8957d6270b4..0000000000000000000000000000000000000000 --- a/authz/src/main/webapp/WEB-INF/jsp/users/user_plus.jsp +++ /dev/null @@ -1,76 +0,0 @@ -<%-- - * - * @author Shengzhao Li ---%> -<%@ page contentType="text/html;charset=UTF-8" language="java" %> -<%@ include file="../comm-header.jsp" %> - - - - Add User - - -Home - -

Add User

- - - -
- - -
- - -

Username, unique.

-
-
-
- - -
- - -

Password, required.

-
-
-
- - -
- - - - - - - -

Select Roles

-
-
- - -
-
-
- -
-
- - -
-
-
- - Cancel -
-
-
- - - - diff --git a/authz/src/main/webapp/WEB-INF/jsp/users/users_overview.jsp b/authz/src/main/webapp/WEB-INF/jsp/users/users_overview.jsp deleted file mode 100644 index a23496e00b3152b29231678df1e158803959e644..0000000000000000000000000000000000000000 --- a/authz/src/main/webapp/WEB-INF/jsp/users/users_overview.jsp +++ /dev/null @@ -1,49 +0,0 @@ -<%-- - * - * @author Shengzhao Li ---%> -<%@ page contentType="text/html;charset=UTF-8" language="java" %> -<%@ include file="../comm-header.jsp" %> - - - - Users - - -Home - -

Users

- - -
-
- -
- -  Total: ${overviewDto.usersSize} -
-
- - - - - - - - - - - - - - - - - -
UsernameRoles[permission]CreateTime
${user.username}${r.roleName} ${r.permissions}${vs.last?'':','}${user.createTime}
- - diff --git a/authz/src/main/webapp/WEB-INF/log4j.xml b/authz/src/main/webapp/WEB-INF/log4j.xml deleted file mode 100644 index 8f19edd372321e6a7d7215bda9f5fe4f33a86b5d..0000000000000000000000000000000000000000 --- a/authz/src/main/webapp/WEB-INF/log4j.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/authz/src/main/webapp/WEB-INF/mkk-servlet.xml b/authz/src/main/webapp/WEB-INF/mkk-servlet.xml deleted file mode 100644 index ce7dffb5c36e86486947ec67bac1385793d23ade..0000000000000000000000000000000000000000 --- a/authz/src/main/webapp/WEB-INF/mkk-servlet.xml +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/authz/src/main/webapp/WEB-INF/web.xml b/authz/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 3c11874861031e17ac0f841b2c9a085f2d428ad8..0000000000000000000000000000000000000000 --- a/authz/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,169 +0,0 @@ - - - - oauth2-shiro - - - - - - - webAppRootKey - oauth2-shiro - - - - - encodingFilter - com.monkeyk.os.web.context.MkkCharacterEncodingFilter - - encoding - UTF-8 - - - forceEncoding - true - - - - encodingFilter - /* - - - - - gzipFilter - - net.sf.ehcache.constructs.web.filter.GzipFilter - - - - gzipFilter - *.css - - - gzipFilter - *.png - - - gzipFilter - *.gif - - - gzipFilter - *.html - - - gzipFilter - *.js - - - - - - shiroFilter - org.springframework.web.filter.DelegatingFilterProxy - - targetFilterLifecycle - true - - - - - shiroFilter - /* - - - - - - sitemesh - com.opensymphony.sitemesh.webapp.SiteMeshFilter - - - sitemesh - /* - - - - ico - image/vnd.microsoft.icon - - - - - contextConfigLocation - classpath:spring/*.xml - - - - - log4jConfigLocation - /WEB-INF/log4j.xml - - - org.springframework.web.util.Log4jConfigListener - - - - - - com.monkeyk.os.web.context.BeanContextLoaderListener - - - - - mkk - org.springframework.web.servlet.DispatcherServlet - 2 - - - mkk - / - - - - - - - - - - - 30 - - - - - loading.jsp - - - - \ No newline at end of file diff --git a/authz/src/main/webapp/loading.jsp b/authz/src/main/webapp/loading.jsp deleted file mode 100644 index 817b6dfb53db76da76bff559c7157a56968d7cbe..0000000000000000000000000000000000000000 --- a/authz/src/main/webapp/loading.jsp +++ /dev/null @@ -1,9 +0,0 @@ -<%-- - * - * @author Shengzhao Li ---%> - -<%@ page contentType="text/html;charset=UTF-8" language="java" %> -<% - request.getRequestDispatcher("index").forward(request, response); -%> \ No newline at end of file diff --git a/authz/src/main/webapp/resources/readme.txt b/authz/src/main/webapp/resources/readme.txt deleted file mode 100644 index d01500c747b85a37b76bcbb499b93436a358ff4a..0000000000000000000000000000000000000000 --- a/authz/src/main/webapp/resources/readme.txt +++ /dev/null @@ -1 +0,0 @@ -All static resources in here. \ No newline at end of file diff --git a/authz/src/test/java/com/monkeyk/os/AuthzApplicationTest.java b/authz/src/test/java/com/monkeyk/os/AuthzApplicationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..9bf633360304b90e029ff350d6018da85b6c7359 --- /dev/null +++ b/authz/src/test/java/com/monkeyk/os/AuthzApplicationTest.java @@ -0,0 +1,22 @@ +package com.monkeyk.os; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 2020/7/13 + * + * @author Shengzhao Li + * @since 2.0.0 + */ +@SpringBootTest +class AuthzApplicationTest { + + + @Test + void contextLoads() { + } + +} \ No newline at end of file diff --git a/authz/src/test/java/com/monkeyk/os/ContextTest.java b/authz/src/test/java/com/monkeyk/os/ContextTest.java index 8a6ac84a439f2b7e629232d65e2d52d0d5fd6fce..1be068c3102364e891929a5b10959a727d6c13ea 100644 --- a/authz/src/test/java/com/monkeyk/os/ContextTest.java +++ b/authz/src/test/java/com/monkeyk/os/ContextTest.java @@ -1,15 +1,17 @@ package com.monkeyk.os; import com.monkeyk.os.domain.shared.BeanProvider; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests; import org.springframework.test.context.transaction.BeforeTransaction; /** * @author Shengzhao Li */ -@ContextConfiguration(locations = {"classpath:/spring/*.xml"}, initializers = {TestApplicationContextInitializer.class}) -public abstract class ContextTest extends AbstractTransactionalTestNGSpringContextTests { +@SpringBootTest +@ActiveProfiles("test") +public abstract class ContextTest extends AbstractTransactionalJUnit4SpringContextTests { @BeforeTransaction diff --git a/authz/src/test/java/com/monkeyk/os/TestApplicationContextInitializer.java b/authz/src/test/java/com/monkeyk/os/TestApplicationContextInitializer.java deleted file mode 100644 index e884f289fe55fca2b40c69eb2c45abf2895bbeb8..0000000000000000000000000000000000000000 --- a/authz/src/test/java/com/monkeyk/os/TestApplicationContextInitializer.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.monkeyk.os; - -import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer; -import org.springframework.context.ApplicationContextInitializer; -import org.springframework.context.support.AbstractApplicationContext; -import org.springframework.core.io.ClassPathResource; - -/** - * @author Shengzhao Li - */ -public class TestApplicationContextInitializer implements ApplicationContextInitializer { - - @Override - public void initialize(AbstractApplicationContext applicationContext) { - PropertyPlaceholderConfigurer propertyPlaceholderConfigurer = new PropertyPlaceholderConfigurer(); - //load database.properties - propertyPlaceholderConfigurer.setLocation(new ClassPathResource("test.properties")); - - applicationContext.addBeanFactoryPostProcessor(propertyPlaceholderConfigurer); - - - } -} \ No newline at end of file diff --git a/authz/src/test/java/com/monkeyk/os/domain/oauth/AccessTokenTest.java b/authz/src/test/java/com/monkeyk/os/domain/oauth/AccessTokenTest.java index 0ab92735402de89bf0611647f9cf8c3178d5cd0e..75d22b0c66205e153707bc4e1a99f82ebd7869dd 100644 --- a/authz/src/test/java/com/monkeyk/os/domain/oauth/AccessTokenTest.java +++ b/authz/src/test/java/com/monkeyk/os/domain/oauth/AccessTokenTest.java @@ -1,13 +1,14 @@ package com.monkeyk.os.domain.oauth; import com.monkeyk.os.infrastructure.DateUtils; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; + import java.net.URLDecoder; import java.net.URLEncoder; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.*; + /** * @author Shengzhao Li diff --git a/authz/src/test/java/com/monkeyk/os/domain/oauth/AuthenticationIdGeneratorTest.java b/authz/src/test/java/com/monkeyk/os/domain/oauth/AuthenticationIdGeneratorTest.java index 5b3bd966448f86bf55ab726b59c748a7ef430f7f..a58d76e782e6aeab1c42ed35c1f6157b935c5193 100644 --- a/authz/src/test/java/com/monkeyk/os/domain/oauth/AuthenticationIdGeneratorTest.java +++ b/authz/src/test/java/com/monkeyk/os/domain/oauth/AuthenticationIdGeneratorTest.java @@ -11,9 +11,10 @@ */ package com.monkeyk.os.domain.oauth; -import org.testng.annotations.Test; -import static org.testng.Assert.assertNotNull; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; /** * 15-6-20 @@ -29,7 +30,7 @@ public class AuthenticationIdGeneratorTest { final String generate = generator.generate("clientid", "username", "authid"); assertNotNull(generate); - System.out.println(generate); +// System.out.println(generate); } } diff --git a/authz/src/test/java/com/monkeyk/os/domain/users/AuthzPasswordEncoderTest.java b/authz/src/test/java/com/monkeyk/os/domain/users/AuthzPasswordEncoderTest.java new file mode 100644 index 0000000000000000000000000000000000000000..e637ef5a513726b056adf2b8fb33915882317b40 --- /dev/null +++ b/authz/src/test/java/com/monkeyk/os/domain/users/AuthzPasswordEncoderTest.java @@ -0,0 +1,47 @@ +package com.monkeyk.os.domain.users; + +import com.monkeyk.os.domain.users.password.ShaPasswordEncoder; +import org.apache.shiro.codec.Base64; +import org.apache.shiro.util.ByteSource; +import org.junit.jupiter.api.Test; + +import java.nio.charset.StandardCharsets; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 2023/9/25 17:38 + * + * @author Shengzhao Li + * @since 2.0.0 + */ +class AuthzPasswordEncoderTest { + + + @Test + void initPwd() { + + //默认 SHA-256 + ShaPasswordEncoder passwordEncoder = new ShaPasswordEncoder(256); + + String adminPwd = "Admin@2015#"; + String salt = "75fbe11d6f70e77b256121d7c3d5c412"; + ByteSource bytesx = ByteSource.Util.bytes(Base64.decode(salt)); + String encode = passwordEncoder.encode(adminPwd, bytesx); + assertNotNull(encode); + assertEquals("7242fb20c63882a6664742e1f9e1ed77e13b74d601cbe5fb11430d24768e808b", encode); +// System.out.println(encode); + assertTrue(passwordEncoder.matches(adminPwd, encode, bytesx)); + + String testPwd = "Test@2015#"; + String testSalt = "5602aa9866ca612e66dbb7f7c9a1d3b7"; + ByteSource bytes = ByteSource.Util.bytes(Base64.decode(testSalt)); + String encode2 = passwordEncoder.encode(testPwd, bytes); + assertNotNull(encode2); + assertEquals("beef64b3218e0c93051119bde87782ed7b932169228c091464055369336f5044", encode2); +// System.out.println(encode2); + assertTrue(passwordEncoder.matches(testPwd, encode2, bytes)); + + } + +} \ No newline at end of file diff --git a/authz/src/test/java/com/monkeyk/os/domain/users/PasswordHandlerTest.java b/authz/src/test/java/com/monkeyk/os/domain/users/PasswordHandlerTest.java index eeb19507c7e2b598362919e86672f24a8dc779a6..0ee1f5be0345f59e6df54dc3e6bf864a22fd27e9 100644 --- a/authz/src/test/java/com/monkeyk/os/domain/users/PasswordHandlerTest.java +++ b/authz/src/test/java/com/monkeyk/os/domain/users/PasswordHandlerTest.java @@ -1,8 +1,9 @@ package com.monkeyk.os.domain.users; -import org.testng.annotations.Test; -import static org.testng.Assert.*; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; /* * @author Shengzhao Li diff --git a/authz/src/test/java/com/monkeyk/os/domain/users/password/MD5PasswordEncoderTest.java b/authz/src/test/java/com/monkeyk/os/domain/users/password/MD5PasswordEncoderTest.java new file mode 100644 index 0000000000000000000000000000000000000000..709259f0afb9809a3840610d8917db0687a481db --- /dev/null +++ b/authz/src/test/java/com/monkeyk/os/domain/users/password/MD5PasswordEncoderTest.java @@ -0,0 +1,48 @@ +package com.monkeyk.os.domain.users.password; + +import org.apache.shiro.codec.Base64; +import org.apache.shiro.crypto.hash.Md5Hash; +import org.apache.shiro.util.ByteSource; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 2023/9/25 16:28 + * + * @author Shengzhao Li + */ +class MD5PasswordEncoderTest { + + @Test + void encode() { + + MD5PasswordEncoder passwordEncoder = new MD5PasswordEncoder(); + String rawPwd = "Admin@2023&"; + String salt = "75fbe11d6f70e77b256121d7c3d5c412"; + ByteSource saltBytes = ByteSource.Util.bytes(Base64.decode(salt)); + String encode = passwordEncoder.encode(rawPwd, saltBytes); + + assertNotNull(encode); +// System.out.println(encode); + assertEquals("fa1250c1ddefb1645a5507e8a7d76c99", encode); + + boolean matches = passwordEncoder.matches(rawPwd, encode, saltBytes); + assertTrue(matches); + + } + + + @Test + void encodeHex() { + + Md5Hash md5Hash = new Md5Hash("admin"); + String hex = md5Hash.toHex(); + assertNotNull(hex); + +// System.out.println(md5Hash.toBase64()); + assertEquals("21232f297a57a5a743894a0e4a801fc3", hex); + + } + +} \ No newline at end of file diff --git a/authz/src/test/java/com/monkeyk/os/domain/users/password/ShaPasswordEncoderTest.java b/authz/src/test/java/com/monkeyk/os/domain/users/password/ShaPasswordEncoderTest.java new file mode 100644 index 0000000000000000000000000000000000000000..739b3d0d66235d176d1eb50aed0898f5c0664725 --- /dev/null +++ b/authz/src/test/java/com/monkeyk/os/domain/users/password/ShaPasswordEncoderTest.java @@ -0,0 +1,70 @@ +package com.monkeyk.os.domain.users.password; + +import org.apache.shiro.codec.Base64; +import org.apache.shiro.util.ByteSource; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 2023/9/25 17:04 + * + * @author Shengzhao Li + * @since 2.0.0 + */ +class ShaPasswordEncoderTest { + + + @Test + void encode256() { + + String rawPwd = "Admin@2023&"; + String salt = "5602aa9866ca612e66dbb7f7c9a1d3b7"; + ShaPasswordEncoder passwordEncoder = new ShaPasswordEncoder(256); + ByteSource bytes = ByteSource.Util.bytes(Base64.decode(salt)); + String encode = passwordEncoder.encode(rawPwd, bytes); + + assertNotNull(encode); + assertEquals("34f37824cf8008f47d777ce9954eff34f2de984f2dc6019952b7bc8d72794ddf", encode); +// System.out.println(encode); + + boolean matches = passwordEncoder.matches(rawPwd, encode, bytes); + assertTrue(matches); + + } + + @Test + void encode384() { + + String rawPwd = "Admin@2023&"; + ShaPasswordEncoder passwordEncoder = new ShaPasswordEncoder(384); + String encode = passwordEncoder.encode(rawPwd, null); + + assertNotNull(encode); + assertEquals("0a32b1b88f37ad21ac65aae125cec7814e5535d1077a4a7e6ccbf48a16800603e04c6909d9a19c68080a248b7ca11034", encode); +// System.out.println(encode); + + boolean matches = passwordEncoder.matches(rawPwd, encode, null); + assertTrue(matches); + + } + + @Test + void encode512() { + + String rawPwd = "Admin@2023&"; + ShaPasswordEncoder passwordEncoder = new ShaPasswordEncoder(512); + String encode = passwordEncoder.encode(rawPwd, null); + + assertNotNull(encode); + assertEquals( + "21dc4b7e4c58ece1d581644231d19d3a09ab5e1047222f28835a05efc3d9293b3d4df18654d0fd985aad592f154cd7fcefab513997ccc1cf9833f362590e63b8", + encode); +// System.out.println(encode); + + boolean matches = passwordEncoder.matches(rawPwd, encode, null); + assertTrue(matches); + + } + +} \ No newline at end of file diff --git a/authz/src/test/java/com/monkeyk/os/infrastructure/jdbc/OauthJdbcRepositoryTest.java b/authz/src/test/java/com/monkeyk/os/infrastructure/jdbc/OauthJdbcRepositoryTest.java index 5d32145e0da142994bca8c47fe372d5b338b1f8d..1ddde278f96c3695241bc0f9c315ed2b7a1b13bd 100644 --- a/authz/src/test/java/com/monkeyk/os/infrastructure/jdbc/OauthJdbcRepositoryTest.java +++ b/authz/src/test/java/com/monkeyk/os/infrastructure/jdbc/OauthJdbcRepositoryTest.java @@ -7,12 +7,14 @@ import com.monkeyk.os.domain.oauth.OauthCode; import com.monkeyk.os.domain.shared.GuidGenerator; import org.apache.oltu.oauth2.as.issuer.MD5Generator; import org.apache.oltu.oauth2.as.issuer.OAuthIssuerImpl; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.testng.annotations.Test; + import java.util.List; -import static org.testng.Assert.*; +import static org.junit.jupiter.api.Assertions.*; + /** * 15-6-13 diff --git a/authz/src/test/java/com/monkeyk/os/infrastructure/jdbc/UsersJdbcAuthzRepositoryTest.java b/authz/src/test/java/com/monkeyk/os/infrastructure/jdbc/UsersJdbcAuthzRepositoryTest.java index 04b6406bd8c204df2365b842b1d99e78d3b43143..5f8de74359cc213a36907ff91f5c62991ebf5344 100644 --- a/authz/src/test/java/com/monkeyk/os/infrastructure/jdbc/UsersJdbcAuthzRepositoryTest.java +++ b/authz/src/test/java/com/monkeyk/os/infrastructure/jdbc/UsersJdbcAuthzRepositoryTest.java @@ -15,12 +15,14 @@ import com.monkeyk.os.ContextTest; import com.monkeyk.os.domain.shared.GuidGenerator; import com.monkeyk.os.domain.users.Roles; import com.monkeyk.os.domain.users.Users; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.testng.annotations.Test; + import java.util.List; -import static org.testng.Assert.*; +import static org.junit.jupiter.api.Assertions.*; + /* * @author Shengzhao Li diff --git a/authz/src/test/java/com/monkeyk/os/web/ShiroTest.java b/authz/src/test/java/com/monkeyk/os/web/ShiroTest.java index 322f648871e208e290faa6696ef90820ef895981..771b129a2258f7df5b26e8cd9d63844e23a84795 100644 --- a/authz/src/test/java/com/monkeyk/os/web/ShiroTest.java +++ b/authz/src/test/java/com/monkeyk/os/web/ShiroTest.java @@ -19,13 +19,15 @@ import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.realm.Realm; import org.apache.shiro.realm.SimpleAccountRealm; import org.apache.shiro.subject.Subject; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + import java.util.ArrayList; import java.util.List; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.*; + /** * 15-6-10 @@ -35,7 +37,9 @@ import static org.testng.Assert.assertTrue; public class ShiroTest { - @Test(enabled = false) +// @Test(enabled = false) + @Test + @Disabled public void login() { String username = "abc"; //init SecurityManager diff --git a/authz/src/test/java/com/monkeyk/os/web/WebUtilsTest.java b/authz/src/test/java/com/monkeyk/os/web/WebUtilsTest.java index 7e38d21dfe64638cdc20a62100d419fc860c93ae..1cecf5c89575adad095ad0391c371292adc8ab66 100644 --- a/authz/src/test/java/com/monkeyk/os/web/WebUtilsTest.java +++ b/authz/src/test/java/com/monkeyk/os/web/WebUtilsTest.java @@ -1,11 +1,11 @@ package com.monkeyk.os.web; import org.apache.oltu.oauth2.common.utils.OAuthUtils; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; -import java.util.Arrays; -import static org.testng.Assert.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotNull; + public class WebUtilsTest { @@ -18,7 +18,7 @@ public class WebUtilsTest { final String[] strings = OAuthUtils.decodeClientAuthenticationHeader(text); assertNotNull(strings); - System.out.println(Arrays.toString(strings)); +// System.out.println(Arrays.toString(strings)); } } \ No newline at end of file diff --git a/authz/src/test/resources/application-test.properties b/authz/src/test/resources/application-test.properties new file mode 100644 index 0000000000000000000000000000000000000000..1007f8c259859b6c88357f063d268304c87f2135 --- /dev/null +++ b/authz/src/test/resources/application-test.properties @@ -0,0 +1,28 @@ +# authz properties +# +spring.application.name=AuthZ +# +# +# Support deploy to a servlet-container +spring.jmx.enabled=false +#Override default +spring.main.allow-bean-definition-overriding=true +# +#JDBC +#Connection +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.datasource.url=jdbc:mysql://localhost:3306/oauth2_shiro_test?autoReconnect=true&autoReconnectForPools=true&useUnicode=true&characterEncoding=utf8&useSSL=false +spring.datasource.username=andaily +spring.datasource.password=andaily +#Datasource properties +spring.datasource.type=com.zaxxer.hikari.HikariDataSource +spring.datasource.hikari.maximum-pool-size=20 +# Logging +# +# +# MVC +spring.thymeleaf.encoding=UTF-8 +spring.thymeleaf.cache=false +# +server.port=8080 +# diff --git a/authz/src/test/resources/log4j.properties b/authz/src/test/resources/log4j.properties deleted file mode 100644 index d1deb90399a5af2f332f609f8defe6d54318e3a9..0000000000000000000000000000000000000000 --- a/authz/src/test/resources/log4j.properties +++ /dev/null @@ -1,13 +0,0 @@ -#\u914D\u7F6E\u6839Logger , file, db -log4j.rootLogger=INFO, stdout - -#\u914D\u7F6E\u65E5\u5FD7\u4FE1\u606F\u8F93\u51FA\u76EE\u7684\u5730(appender) -#\u63A7\u5236\u53F0\u8F93\u51FA -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -#\u63A7\u5236\u53F0\u8F93\u51FA\u7684\u9009\u9879(\u53EF\u9009) -#log4j.appender.stdout.Threshold=INFO -#log4j.appender.stdout.ImmediateFlush=true -log4j.appender.stdout.Target=System.out -#\u63A7\u5236\u53F0\u8F93\u51FA\u7684\u683C\u5F0F -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n diff --git a/authz/src/test/resources/test.properties b/authz/src/test/resources/test.properties deleted file mode 100644 index 6044cc49aee9c1a479bd09f10f37446a16a9d782..0000000000000000000000000000000000000000 --- a/authz/src/test/resources/test.properties +++ /dev/null @@ -1,10 +0,0 @@ -#JDBC configuration information -jdbc.driverClassName=com.mysql.jdbc.Driver -############ -# localhost -############ -jdbc.url=jdbc:mysql://localhost:3306/oauth2_shiro_test?autoReconnect=true&autoReconnectForPools=true&useUnicode=true&characterEncoding=utf8 -jdbc.username=andaily -jdbc.password=andaily - - diff --git a/core/pom.xml b/core/pom.xml index fc5d4997077a15a9968bcf4ee1d4f36d6c684f69..a8ef56ad4f7d420d648f59df763f7ab6d87821a0 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -6,7 +6,7 @@ com.monkeyk core - 0.3 + 2.0.0 ${project.artifactId} @@ -14,9 +14,10 @@ oltu and shiro [CORE] + 1.8 UTF-8 - 3.2.2.RELEASE + 5.3.13 1.0.2 1.11.0 @@ -30,8 +31,8 @@ maven-compiler-plugin 3.2 - 1.7 - 1.7 + ${java.version} + ${java.version} UTF-8 @@ -116,12 +117,12 @@ - - org.slf4j - slf4j-log4j12 - 1.7.5 - compile - + + + + + + @@ -130,17 +131,23 @@ ${spring.version} test + + + + + + + + + + + + - org.testng - testng - 6.1.1 + org.junit.jupiter + junit-jupiter + 5.6.2 test - - - junit - junit - - diff --git a/core/src/main/java/com/monkeyk/os/domain/oauth/ClientDetails.java b/core/src/main/java/com/monkeyk/os/domain/oauth/ClientDetails.java index 6b92dfd550f6b2b2b1b24f9e15ce5f29ab99d7e8..0b20da97083bce59a8085647a079748e00baa3ec 100644 --- a/core/src/main/java/com/monkeyk/os/domain/oauth/ClientDetails.java +++ b/core/src/main/java/com/monkeyk/os/domain/oauth/ClientDetails.java @@ -22,6 +22,8 @@ public class ClientDetails extends BasicClientInfo { /** * 客户端所拥有的资源ID(resource-id), 至少有一个, * 多个ID时使用逗号(,)分隔, 如: os,mobile + *

+ * 可选,可使用一个指定的 resourceIds */ private String resourceIds; @@ -33,9 +35,9 @@ public class ClientDetails extends BasicClientInfo { */ private String grantTypes; - /* - * Shiro roles - * */ + /** + * Shiro roles + * */ private String roles; /** diff --git a/core/src/main/java/com/monkeyk/os/domain/oauth/Constants.java b/core/src/main/java/com/monkeyk/os/domain/oauth/Constants.java index 72d65faafe3c6c6a27784de1d56aa204f1a110e6..77dc7bacd32d2ab1341c6f19edcbbb0a38db954c 100644 --- a/core/src/main/java/com/monkeyk/os/domain/oauth/Constants.java +++ b/core/src/main/java/com/monkeyk/os/domain/oauth/Constants.java @@ -7,19 +7,40 @@ package com.monkeyk.os.domain.oauth; * * @author Shengzhao Li */ -public abstract class Constants { +public interface Constants { - public static final String REQUEST_USERNAME = "username"; - public static final String REQUEST_PASSWORD = "password"; + String REQUEST_USERNAME = "username"; + String REQUEST_PASSWORD = "password"; - public static final String REQUEST_USER_OAUTH_APPROVAL = "user_oauth_approval"; + String REQUEST_USER_OAUTH_APPROVAL = "user_oauth_approval"; - public static final String OAUTH_LOGIN_VIEW = "oauth_login"; - public static final String OAUTH_APPROVAL_VIEW = "oauth_approval"; + String OAUTH_LOGIN_VIEW = "oauth_login"; + String OAUTH_APPROVAL_VIEW = "oauth_approval"; - private Constants() { - } + /** + * label: jwt + * + * @since 2.0.0 + */ + String JWT = "jwt"; + + + /** + * Version + * 与pom.xml中一致 + * + * @since 2.0.0 + */ + String VERSION = "2.0.0"; + + + /** + * Fixed keyId + * + * @since 2.0.0 + */ + String DEFAULT_KEY_ID = "oauth2-shiro-keyid"; } diff --git a/core/src/main/java/com/monkeyk/os/domain/shared/BeanProvider.java b/core/src/main/java/com/monkeyk/os/domain/shared/BeanProvider.java index 167f91418e351f92a2ce9c553a6c2f84bfb57ca0..4dc5a1d47bf27ed7a7d333ccc26897d0fad87ce4 100644 --- a/core/src/main/java/com/monkeyk/os/domain/shared/BeanProvider.java +++ b/core/src/main/java/com/monkeyk/os/domain/shared/BeanProvider.java @@ -1,5 +1,6 @@ package com.monkeyk.os.domain.shared; +import org.springframework.beans.factory.BeanFactory; import org.springframework.context.ApplicationContext; /** @@ -9,11 +10,17 @@ public abstract class BeanProvider { private static ApplicationContext applicationContext; + /** + * @since 2.0.0 + */ + private static BeanFactory beanFactory; + private BeanProvider() { } public static void initialize(ApplicationContext applicationContext) { BeanProvider.applicationContext = applicationContext; + BeanProvider.beanFactory = applicationContext.getAutowireCapableBeanFactory(); } /** @@ -24,18 +31,19 @@ public abstract class BeanProvider { * @return Bean instance */ public static T getBean(Class clazz) { - if (applicationContext == null) { + if (beanFactory == null) { return null; } - return applicationContext.getBean(clazz); + return beanFactory.getBean(clazz); } @SuppressWarnings("unchecked") public static T getBean(String beanId) { - if (applicationContext == null) { + if (beanFactory == null) { return null; } - return (T) applicationContext.getBean(beanId); + return (T) beanFactory.getBean(beanId); } + } \ No newline at end of file diff --git a/core/src/main/java/com/monkeyk/os/domain/shared/GuidGenerator.java b/core/src/main/java/com/monkeyk/os/domain/shared/GuidGenerator.java index be5a69f057ead7c3c2cdff16ade13c73bb5d8e47..935fe10881f7e2dcafb50ec220f3e13544f48558 100644 --- a/core/src/main/java/com/monkeyk/os/domain/shared/GuidGenerator.java +++ b/core/src/main/java/com/monkeyk/os/domain/shared/GuidGenerator.java @@ -1,6 +1,8 @@ package com.monkeyk.os.domain.shared; import org.apache.commons.lang.RandomStringUtils; +import org.apache.shiro.crypto.SecureRandomNumberGenerator; +import org.apache.shiro.util.ByteSource; /** * @author Shengzhao Li @@ -8,9 +10,37 @@ import org.apache.commons.lang.RandomStringUtils; public abstract class GuidGenerator { + /** + * salt generator + * + * @since 2.0.0 + */ + private static final SecureRandomNumberGenerator SALT_NUMBER_GENERATOR = new SecureRandomNumberGenerator(); + private GuidGenerator() { } + + /** + * 生成一个随机的 salt + * + * @return ByteSource + * @since 2.0.0 + */ + public static ByteSource nextSalt() { + return SALT_NUMBER_GENERATOR.nextBytes(); + } + + /** + * 生成一个随机的 salt, hex + * + * @return hex string + * @since 2.0.0 + */ + public static String nextSaltHex() { + return nextSalt().toHex(); + } + public static String generate() { return RandomStringUtils.random(32, true, true); } diff --git a/core/src/main/java/com/monkeyk/os/domain/users/Users.java b/core/src/main/java/com/monkeyk/os/domain/users/Users.java index 979197e1967e53478e0ac70233f2e44029a25b14..199d24ec07652202ac136d8784ca4c94bb86a93b 100644 --- a/core/src/main/java/com/monkeyk/os/domain/users/Users.java +++ b/core/src/main/java/com/monkeyk/os/domain/users/Users.java @@ -23,6 +23,12 @@ public class Users extends AbstractDomain { private String username; private String password; + /** + * 密码 salt, 盐值 + * + * @since 2.0.0 + */ + private String passwordSalt; private boolean defaultUser; private Date lastLoginTime; @@ -30,6 +36,23 @@ public class Users extends AbstractDomain { public Users() { } + /** + * @return salt or null + * @since 2.0.0 + */ + public String passwordSalt() { + return passwordSalt; + } + + /** + * @param passwordSalt salt + * @return this + * @since 2.0.0 + */ + public Users passwordSalt(String passwordSalt) { + this.passwordSalt = passwordSalt; + return this; + } public List rolesList() { return usersRepository.findUsersRolesList(this.guid); diff --git a/core/src/main/java/com/monkeyk/os/infrastructure/ThreadLocalHolder.java b/core/src/main/java/com/monkeyk/os/infrastructure/ThreadLocalHolder.java index ed8a70671ee884f854adfc905b2857854668d8f6..2965134426fc89b7aacbe5026b350d65c8339640 100644 --- a/core/src/main/java/com/monkeyk/os/infrastructure/ThreadLocalHolder.java +++ b/core/src/main/java/com/monkeyk/os/infrastructure/ThreadLocalHolder.java @@ -1,21 +1,23 @@ package com.monkeyk.os.infrastructure; +import org.springframework.core.NamedThreadLocal; + /** * @author Shengzhao Li */ public abstract class ThreadLocalHolder { - private static ThreadLocal clientIpThreadLocal = new ThreadLocal<>(); + private static NamedThreadLocal clientIpThreadLocal = new NamedThreadLocal<>("clientIpThreadLocal"); - /* + /** * 设置当前访问的IP地址 * */ public static void clientIp(String clientIp) { clientIpThreadLocal.set(clientIp); } - /* + /** * 获取访问的IP地址 * */ public static String clientIp() { diff --git a/core/src/main/java/com/monkeyk/os/infrastructure/jdbc/UsersRowMapper.java b/core/src/main/java/com/monkeyk/os/infrastructure/jdbc/UsersRowMapper.java index 212ebd9a391042bf6b6bb89be49fb046287a936d..e5ff62ceeedeec21d017a3ed4f82ae3b2281234d 100644 --- a/core/src/main/java/com/monkeyk/os/infrastructure/jdbc/UsersRowMapper.java +++ b/core/src/main/java/com/monkeyk/os/infrastructure/jdbc/UsersRowMapper.java @@ -21,6 +21,8 @@ public class UsersRowMapper implements RowMapper { Users users = new Users() .username(rs.getString("username")) .password(rs.getString("password")) + // salt 不需要查询出来 +// .passwordSalt(rs.getString("password_salt")) .defaultUser(rs.getBoolean("default_user")) .lastLoginTime(rs.getTimestamp("last_login_time")); diff --git a/core/src/main/java/com/monkeyk/os/infrastructure/shiro/MkkJdbcRealm.java b/core/src/main/java/com/monkeyk/os/infrastructure/shiro/MkkJdbcRealm.java index 3b94a02700ecae89aad0432826bb0aa48bdbc528..724e936a1c927e00dad80f8f098d567250ad3ba5 100644 --- a/core/src/main/java/com/monkeyk/os/infrastructure/shiro/MkkJdbcRealm.java +++ b/core/src/main/java/com/monkeyk/os/infrastructure/shiro/MkkJdbcRealm.java @@ -14,7 +14,7 @@ import org.springframework.beans.factory.InitializingBean; */ public class MkkJdbcRealm extends JdbcRealm implements InitializingBean { - public static final String AUTHENTICATION_QUERY = "select password from users where archived = 0 and username = ?"; + public static final String AUTHENTICATION_QUERY = "select password, password_salt from users where archived = 0 and username = ?"; public static final String USER_ROLES_QUERY = "select r.role_name from user_roles ur,users u,roles r where ur.users_id = u.id and ur.roles_id = r.id and u.username = ?"; @@ -23,13 +23,15 @@ public class MkkJdbcRealm extends JdbcRealm implements InitializingBean { public MkkJdbcRealm() { super(); + //support salt since 2.0.0 + setSaltStyle(SaltStyle.COLUMN); } /** * 根据实现的需要, 可以修改具体使用时的查询语句 * - * @throws Exception + * @throws Exception e */ @Override public void afterPropertiesSet() throws Exception { diff --git a/core/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2Token.java b/core/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2Token.java index 106daa67239e40494ee0e9f56d8cebe90de1f284..f72b9b939a37a2bd9c8d58ddbf9d26d11de19ea5 100644 --- a/core/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2Token.java +++ b/core/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2Token.java @@ -13,13 +13,19 @@ public class OAuth2Token implements RememberMeAuthenticationToken { private static final long serialVersionUID = 8587854556973099598L; - // the service access_token + /** + * the service access_token + */ private String accessToken; - // the user identifier, username + /** + * the user identifier, username + */ private String userId; - // is the user in a remember me mode ? + /** + * is the user in a remember me mode ? + */ private boolean rememberMe = false; diff --git a/core/src/test/java/com/monkeyk/os/TestApplicationContextInitializer.java b/core/src/test/java/com/monkeyk/os/TestApplicationContextInitializer.java deleted file mode 100644 index e884f289fe55fca2b40c69eb2c45abf2895bbeb8..0000000000000000000000000000000000000000 --- a/core/src/test/java/com/monkeyk/os/TestApplicationContextInitializer.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.monkeyk.os; - -import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer; -import org.springframework.context.ApplicationContextInitializer; -import org.springframework.context.support.AbstractApplicationContext; -import org.springframework.core.io.ClassPathResource; - -/** - * @author Shengzhao Li - */ -public class TestApplicationContextInitializer implements ApplicationContextInitializer { - - @Override - public void initialize(AbstractApplicationContext applicationContext) { - PropertyPlaceholderConfigurer propertyPlaceholderConfigurer = new PropertyPlaceholderConfigurer(); - //load database.properties - propertyPlaceholderConfigurer.setLocation(new ClassPathResource("test.properties")); - - applicationContext.addBeanFactoryPostProcessor(propertyPlaceholderConfigurer); - - - } -} \ No newline at end of file diff --git a/core/src/test/java/com/monkeyk/os/domain/shared/GuidGeneratorTest.java b/core/src/test/java/com/monkeyk/os/domain/shared/GuidGeneratorTest.java index 22b2a6b0d7b71496d7417ea9d9731a350a9fdc65..bf2bbea2ac56e188ca9e0bdf475d5f67a44969bd 100644 --- a/core/src/test/java/com/monkeyk/os/domain/shared/GuidGeneratorTest.java +++ b/core/src/test/java/com/monkeyk/os/domain/shared/GuidGeneratorTest.java @@ -1,20 +1,40 @@ package com.monkeyk.os.domain.shared; -import org.testng.annotations.Test; -import static org.testng.Assert.*; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; /* - * @author Shengzhao Li - */ + * @author Shengzhao Li + */ public class GuidGeneratorTest { + + @Test + void generate() { + + String uuid = GuidGenerator.generate(); + assertNotNull(uuid); +// System.out.println(uuid); + + } + + + @Test + void nextSaltHex() { + + String salt = GuidGenerator.nextSaltHex(); + assertNotNull(salt); + assertTrue(salt.length() > 8); + } + @Test public void testGenerateClientId() throws Exception { final String clientId = GuidGenerator.generateClientId(); assertNotNull(clientId); assertTrue(clientId.length() == 20); - System.out.println(clientId); +// System.out.println(clientId); } @Test @@ -22,10 +42,10 @@ public class GuidGeneratorTest { final String clientSecret = GuidGenerator.generateClientSecret(); assertNotNull(clientSecret); assertTrue(clientSecret.length() == 20); - System.out.println(clientSecret); - - for (int i = 0; i < 5; i++) { - System.out.println(GuidGenerator.generateClientSecret()); - } +// System.out.println(clientSecret); +// +// for (int i = 0; i < 5; i++) { +// System.out.println(GuidGenerator.generateClientSecret()); +// } } } \ No newline at end of file diff --git a/core/src/test/java/com/monkeyk/os/domain/users/UsersTest.java b/core/src/test/java/com/monkeyk/os/domain/users/UsersTest.java new file mode 100644 index 0000000000000000000000000000000000000000..ed4b8c408a71e2c5f3ed42e1f1b8df8ad2d4c909 --- /dev/null +++ b/core/src/test/java/com/monkeyk/os/domain/users/UsersTest.java @@ -0,0 +1,33 @@ +package com.monkeyk.os.domain.users; + +import org.apache.shiro.crypto.SecureRandomNumberGenerator; +import org.apache.shiro.util.ByteSource; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 2023/9/25 22:18 + * + * @author Shengzhao Li + * @since 2.0.0 + */ +class UsersTest { + + + @Test + void passwordSalt() { + + SecureRandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator(); + ByteSource byteSource = randomNumberGenerator.nextBytes(); + assertNotNull(byteSource); + assertNotNull(byteSource.toHex()); +// System.out.println(byteSource.toHex()); + + + ByteSource bytes = ByteSource.Util.bytes(byteSource.toHex()); + assertNotNull(bytes); + + } + +} \ No newline at end of file diff --git a/core/src/test/java/com/monkeyk/os/infrastructure/DateUtilsTest.java b/core/src/test/java/com/monkeyk/os/infrastructure/DateUtilsTest.java index 4d63fff1b1a647facf438a4866fd33cb0b6d602d..dbde7d557f8366ea70d26f837de45bf4398f4cbd 100644 --- a/core/src/test/java/com/monkeyk/os/infrastructure/DateUtilsTest.java +++ b/core/src/test/java/com/monkeyk/os/infrastructure/DateUtilsTest.java @@ -1,8 +1,9 @@ package com.monkeyk.os.infrastructure; -import org.testng.annotations.Test; -import static org.testng.Assert.*; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; /** * @author Shengzhao Li diff --git a/core/src/test/java/com/monkeyk/os/infrastructure/Jose4JTest.java b/core/src/test/java/com/monkeyk/os/infrastructure/Jose4JTest.java index 8a6501a36650cb680c99073b15a319c67ea7a9cb..b935cdde911a3a93e1923e8b7e0bfef26490bed0 100644 --- a/core/src/test/java/com/monkeyk/os/infrastructure/Jose4JTest.java +++ b/core/src/test/java/com/monkeyk/os/infrastructure/Jose4JTest.java @@ -1,6 +1,8 @@ package com.monkeyk.os.infrastructure; import com.monkeyk.os.domain.shared.GuidGenerator; +import org.apache.commons.lang.RandomStringUtils; +import org.jose4j.jwa.AlgorithmConstraints; import org.jose4j.jwe.ContentEncryptionAlgorithmIdentifiers; import org.jose4j.jwe.JsonWebEncryption; import org.jose4j.jwe.KeyManagementAlgorithmIdentifiers; @@ -12,12 +14,16 @@ import org.jose4j.jwt.consumer.JwtConsumer; import org.jose4j.jwt.consumer.JwtConsumerBuilder; import org.jose4j.keys.AesKey; import org.jose4j.keys.EllipticCurves; -import org.testng.annotations.Test; +import org.jose4j.keys.HmacKey; +import org.junit.jupiter.api.Test; + +import java.nio.charset.StandardCharsets; import java.security.Key; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; +import static com.monkeyk.os.domain.oauth.Constants.DEFAULT_KEY_ID; +import static org.junit.jupiter.api.Assertions.*; + /** * 2016/12/15 @@ -27,9 +33,42 @@ import static org.testng.Assert.assertNotNull; public class Jose4JTest { + /** + * @throws Exception e + * @since 2.0.0 + */ + @Test + void hmacFlow() throws Exception { + + String hmacKey = "Bl0depAUL2DRPZnR0DJThK9a9KSJF4Xr"; +// System.out.println(keyStr); + + HmacKey key = new HmacKey(hmacKey.getBytes(StandardCharsets.UTF_8)); + + JsonWebSignature jws = new JsonWebSignature(); + jws.setKeyIdHeaderValue(DEFAULT_KEY_ID); + jws.setKey(key); + jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.HMAC_SHA256); + + JwtClaims claims = new JwtClaims(); +// claims.setSubject("zhangsan"); +// claims.setIssuer("https://myoidc.com"); + claims.setIssuedAtToNow(); + claims.setGeneratedJwtId(); +// claims.setExpirationTimeMinutesInTheFuture(10); + claims.setAudience("oauth2-shiro"); + jws.setPayload(claims.toJson()); + + String token = jws.getCompactSerialization(); + assertNotNull(token); +// System.out.println(token); + + } + + /* - * AES 加密与解密, 128位 - * */ + * AES 加密与解密, 128位 + * */ @Test public void aesEncryptDecrypt128() throws Exception { @@ -61,8 +100,8 @@ public class Jose4JTest { /* - * AES 加密与解密, 256位 - * */ + * AES 加密与解密, 256位 + * */ @Test public void aesEncryptDecrypt256() throws Exception { @@ -132,7 +171,7 @@ public class Jose4JTest { .setRequireSubject() // the JWT must have a subject claim .setExpectedIssuer("Issuer") // whom the JWT needs to have been issued by .setExpectedAudience("Audience") // to whom the JWT is intended for - //公钥 + //公钥 .setVerificationKey(jwk.getKey()) // verify the signature with the public key .build(); // create the JwtConsumer instance @@ -215,9 +254,9 @@ public class Jose4JTest { .setRequireSubject() // the JWT must have a subject claim .setExpectedIssuer("Issuer") // whom the JWT needs to have been issued by .setExpectedAudience("Audience") // to whom the JWT is intended for - //解密的私钥 + //解密的私钥 .setDecryptionKey(receiverJwk.getPrivateKey()) // decrypt with the receiver's private key - //验签的公钥 + //验签的公钥 .setVerificationKey(sendJwk.getPublicKey()) // verify the signature with the sender's public key .build(); // create the JwtConsumer instance diff --git a/core/src/test/resources/log4j.properties b/core/src/test/resources/log4j.properties deleted file mode 100644 index d1deb90399a5af2f332f609f8defe6d54318e3a9..0000000000000000000000000000000000000000 --- a/core/src/test/resources/log4j.properties +++ /dev/null @@ -1,13 +0,0 @@ -#\u914D\u7F6E\u6839Logger , file, db -log4j.rootLogger=INFO, stdout - -#\u914D\u7F6E\u65E5\u5FD7\u4FE1\u606F\u8F93\u51FA\u76EE\u7684\u5730(appender) -#\u63A7\u5236\u53F0\u8F93\u51FA -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -#\u63A7\u5236\u53F0\u8F93\u51FA\u7684\u9009\u9879(\u53EF\u9009) -#log4j.appender.stdout.Threshold=INFO -#log4j.appender.stdout.ImmediateFlush=true -log4j.appender.stdout.Target=System.out -#\u63A7\u5236\u53F0\u8F93\u51FA\u7684\u683C\u5F0F -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n diff --git a/others/database/initial-db.ddl b/others/database/initial-db.ddl index d3d2159b2ebc6a68430d3202513adc34177e8cce..7587c18b1739575e251bfb8ac6ec399a81dc2177 100644 --- a/others/database/initial-db.ddl +++ b/others/database/initial-db.ddl @@ -4,12 +4,12 @@ set names utf8; --- Two users: admin/admin; test/test +-- Two users: admin/Admin@2015# test/Test@2015# truncate users; -insert into users(id,guid,create_time,username,password,default_user) +insert into users(id,guid,create_time,username,password,password_salt,default_user) values -(21,uuid(),now(),'admin','21232f297a57a5a743894a0e4a801fc3',1), -(22,uuid(),now(),'test','098f6bcd4621d373cade4e832627b4f6',0); +(21,uuid(),now(),'admin','7242fb20c63882a6664742e1f9e1ed77e13b74d601cbe5fb11430d24768e808b','75fbe11d6f70e77b256121d7c3d5c412',1), +(22,uuid(),now(),'test','beef64b3218e0c93051119bde87782ed7b932169228c091464055369336f5044','5602aa9866ca612e66dbb7f7c9a1d3b7',0); -- Two roles: User,Admin @@ -48,7 +48,7 @@ insert into oauth_client_details(client_id, client_secret, client_name, client_u client_icon_uri, resource_ids, scope, grant_types, redirect_uri, roles) values -('test','test','Test Client','http://andaily.com', +('test-client','Test@2015$$','Test Client','http://andaily.com', 'http://andaily.com/favicon.ico','os-resource','read write','authorization_code,password,refresh_token,client_credentials', 'http://localhost:7777/spring-oauth-client/authorization_code_callback','22'); -- Mobile resource client details @@ -56,7 +56,7 @@ insert into oauth_client_details(client_id, client_secret, client_name, client_u client_icon_uri, resource_ids, scope, grant_types, redirect_uri, roles) values -('mobile','mobile','Mobile Client','http://andaily.com', +('mobile-client','Mobile@2015$$','Mobile Client','http://andaily.com', 'http://andaily.com/favicon.ico','mobile-resource','read write','password,refresh_token', 'http://localhost:7777/spring-oauth-client/authorization_code_callback','22'); diff --git a/others/database/oauth2-shiro.ddl b/others/database/oauth2-shiro.ddl index 9ac99582f619d5a406ed8929f144536493e7f51e..31000e5d5515beb442e85b5c6af24db17081599a 100644 --- a/others/database/oauth2-shiro.ddl +++ b/others/database/oauth2-shiro.ddl @@ -23,6 +23,7 @@ CREATE TABLE users ( archived tinyint(1) default '0', version int(11) DEFAULT 0, password varchar(255) not null, + password_salt varchar(255), username varchar(255) not null unique, default_user tinyint(1) default '0', last_login_time datetime , @@ -87,7 +88,7 @@ create table oauth_client_details ( Drop table if exists oauth_access_token; create table oauth_access_token ( create_time timestamp default now(), - token_id VARCHAR(255) unique, + token_id text, token_expired_seconds INTEGER default -1, authentication_id VARCHAR(255), username VARCHAR(255), diff --git a/others/oauth_test.txt b/others/oauth_test.txt index 7ae9d4baecff97a93c2c8922e193fe47e118fc69..a54d888e8e8e7c50603304c9c0db00dd5479920d 100644 --- a/others/oauth_test.txt +++ b/others/oauth_test.txt @@ -1,43 +1,99 @@ -Make sure project modules context path as below: -authz -> authz -resources -> rs +Make sure project modules setting as below: +authz -> port: 8080 +resources -> port: 8083 --- Test grant_type = 'authorization_code' , get code +-- Test grant_type = 'authorization_code' , get code [GET] -http://localhost:8080/authz/oauth/authorize?response_type=code&scope=read write&client_id=test&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fspring-oauth-client%2Fauthorization_code_callback&state=09876999 +http://localhost:8080/oauth/authorize?response_type=code&scope=read write&client_id=test-client&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fspring-oauth-client%2Fauthorization_code_callback&state=09876999 -http://localhost:8080/authz/oauth/authorize?response_type=code&scope=read%20write&client_id=test&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fspring-oauth-client%2Fauthorization_code_callback&state=swss58522555 +http://localhost:8080/oauth/authorize?response_type=code&scope=read%20write&client_id=test-client&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fspring-oauth-client%2Fauthorization_code_callback&state=swss58522555 --- Test grant_type = 'token' --- implicit +-- Test grant_type = 'token' [GET] +-- implicit [deprecated] -http://localhost:8080/authz/oauth/authorize?response_type=token&scope=read write&client_id=test&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fspring-oauth-client%2Fauthorization_code_callback +http://localhost:8080/oauth/authorize?response_type=token&scope=read write&client_id=test-client&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fspring-oauth-client%2Fauthorization_code_callback -- Test from 'code' get 'token' [POST] -http://localhost:8080/authz/oauth/token?client_id=test&client_secret=test&grant_type=authorization_code&code=ac0bd18863b07adfb518cc6e6dfcfcab&redirect_uri=http://localhost:8080/os/oauth/authorize?response_type=code&scope=read%20write&client_id=test&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fspring-oauth-client%2Fauthorization_code_callback&state=09876999 - - +http://localhost:8080/oauth/token?client_id=test-client&client_secret=Test@2015$$&grant_type=authorization_code&code=ac0bd18863b07adfb518cc6e6dfcfcab&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fspring-oauth-client%2Fauthorization_code_callback + +curl --location 'http://localhost:8080/oauth/token' \ +--header 'Content-Type: application/x-www-form-urlencoded' \ +--data-urlencode 'client_id=test-client' \ +--data-urlencode 'client_secret=Test@2015$$' \ +--data-urlencode 'grant_type=authorization_code' \ +--data-urlencode 'redirect_uri=http://localhost:7777/spring-oauth-client/authorization_code_callback' \ +--data-urlencode 'code=52aa9d9cb8e62649e887e745fda94fa7' + +response +{ + "access_token": "eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiY2I2ZmIzYTFmNzA0OGU3NDYxZjcwYWI2OGNkYTk1ZjUiLCJpYXQiOjE2OTU4NjYzODIsImV4cCI6MTY5NTkwOTU4MiwiYXVkIjoidGVzdC1jbGllbnQifQ.NqJe-j7p3UC2gJlBJ-tKB4GrFsW9OR-GyxMfm4LIfwQ", + "refresh_token": "019b043ddcf5994220617b6795c5216a", + "token_type": "Bearer", + "expires_in": 43199 +} -- Test grant_type='password' [POST] -http://localhost:8080/authz/oauth/token?client_id=test&client_secret=test&grant_type=password&scope=read write&username=test&password=test - +http://localhost:8080/oauth/token?client_id=test-client&client_secret=Test@2015$$&grant_type=password&scope=read write&username=test&password=Test@2015# + +curl --location 'http://localhost:8080/oauth/token' \ +--header 'Content-Type: application/x-www-form-urlencoded' \ +--data-urlencode 'client_id=mobile-client' \ +--data-urlencode 'client_secret=Mobile@2015$$' \ +--data-urlencode 'grant_type=password' \ +--data-urlencode 'username=test' \ +--data-urlencode 'password=Test@2015#' \ +--data-urlencode 'scope=read' + +response +{ + "access_token": "eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiNDllOTQ0N2E2ZGJlMTUyZTBkMmE2YjYzNDRmZmQyNWQiLCJpYXQiOjE2OTU4NjY1NDAsImV4cCI6MTY5NTkwOTc0MCwiYXVkIjoibW9iaWxlLWNsaWVudCJ9.WjB1lvsccXXBJiOeHMuvo-kZmpfpi1YQgU8NuGYmR70", + "refresh_token": "46a2017568aee3875a42f7c2234f4b3d", + "token_type": "Bearer", + "expires_in": 43199 +} -- Test grant_type='client_credentials' [POST] -http://localhost:8080/authz/oauth/token?client_id=test&client_secret=test&grant_type=client_credentials&scope=read - +http://localhost:8080/oauth/token?client_id=test-client&client_secret=Test@2015$$&grant_type=client_credentials&scope=read + +curl --location 'http://localhost:8080/oauth/token' \ +--header 'Content-Type: application/x-www-form-urlencoded' \ +--data-urlencode 'client_id=test-client' \ +--data-urlencode 'client_secret=Test@2015$$' \ +--data-urlencode 'grant_type=client_credentials' \ +--data-urlencode 'scope=read' + +response +{ + "access_token": "eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0LWNsaWVudCIsImp0aSI6IjlkNTZhMjFhYzNhZGMzMWQyYzRjZDJlOWEyNTNkY2RmIiwiaWF0IjoxNjk1ODY2NjA5LCJleHAiOjE2OTU5MDk4MDksImF1ZCI6InRlc3QtY2xpZW50In0.brapFTd_HiPfrlKZWOK9MXOFKrDRD7v2dqXnGU7nkjI", + "token_type": "Bearer", + "expires_in": 43199 +} -- Test grant_type='refresh_token' [POST] -http://localhost:8080/authz/oauth/token?client_id=test&client_secret=test&grant_type=refresh_token&refresh_token=b36f4978a1724aa8af8960f58abe3ba1 - - +http://localhost:8080/oauth/token?client_id=test-client&client_secret=Test@2015$$&grant_type=refresh_token&refresh_token=b36f4978a1724aa8af8960f58abe3ba1 + +curl --location 'http://localhost:8080/oauth/token' \ +--header 'Content-Type: application/x-www-form-urlencoded' \ +--data-urlencode 'client_id=test-client' \ +--data-urlencode 'client_secret=Test@2015$$' \ +--data-urlencode 'grant_type=refresh_token' \ +--data-urlencode 'refresh_token=8c46797a0101800626270ce6579c84fa' + +response +{ + "access_token": "eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiZTUyYTExZGU4Y2EwNGUwOTI1Y2RhNDMxNDYwN2NmZGUiLCJpYXQiOjE2OTU4NjY2NzksImV4cCI6MTY5NTkwOTg3OSwiYXVkIjoidGVzdC1jbGllbnQifQ.qLkvnAartpxkiFfeMwnzrK61ihJtXAu6ml5tFU8O-NU", + "refresh_token": "bb277d6ba38bbf5d6facae92eb29e286", + "token_type": "Bearer", + "expires_in": 43199 +} -- -Test-Page URL: http://localhost:8080/authz/resources/oauth_test.html +Test-Page URL: http://localhost:8080/html/oauth_test.html -- @@ -46,7 +102,16 @@ Test-Page URL: http://localhost:8080/authz/resources/oauth_test.html -- Test resource -http://localhost:8080/rs/rs/username?access_token=6b7b0726e603cd04c797d5b28c7be4c4 +http://localhost:8083/rs/username [GET] + +curl --location 'http://localhost:8083/rs/username' \ +--header 'Authorization: Bearer eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiZTUyYTExZGU4Y2EwNGUwOTI1Y2RhNDMxNDYwN2NmZGUiLCJpYXQiOjE2OTU4NjY2NzksImV4cCI6MTY5NTkwOTg3OSwiYXVkIjoidGVzdC1jbGllbnQifQ.qLkvnAartpxkiFfeMwnzrK61ihJtXAu6ml5tFU8O-NU' + +response +{ + "clientId": "test-client", + "username": "test" +} -- @@ -54,11 +119,34 @@ http://localhost:8080/rs/rs/username?access_token=6b7b0726e603cd04c797d5b28c7be4 -- -- Test grant_type='password' [POST] -http://localhost:8080/authz/oauth/token?client_id=mobile&client_secret=mobile&grant_type=password&scope=read write&username=test&password=test - - +http://localhost:8080/oauth/token?client_id=mobile-client&client_secret=Mobile@2015$$&grant_type=password&scope=read write&username=test&password=Test@2015# + +curl --location 'http://localhost:8080/oauth/token' \ +--header 'Content-Type: application/x-www-form-urlencoded' \ +--data-urlencode 'client_id=mobile-client' \ +--data-urlencode 'client_secret=Mobile@2015$$' \ +--data-urlencode 'grant_type=password' \ +--data-urlencode 'username=test' \ +--data-urlencode 'password=Test@2015#' \ +--data-urlencode 'scope=read' + + +response +{ + "access_token": "eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiNDllOTQ0N2E2ZGJlMTUyZTBkMmE2YjYzNDRmZmQyNWQiLCJpYXQiOjE2OTU4NjY1NDAsImV4cCI6MTY5NTkwOTc0MCwiYXVkIjoibW9iaWxlLWNsaWVudCJ9.WjB1lvsccXXBJiOeHMuvo-kZmpfpi1YQgU8NuGYmR70", + "refresh_token": "46a2017568aee3875a42f7c2234f4b3d", + "token_type": "Bearer", + "expires_in": 43199 +} -- Test mobile resource -http://qc8.cc:8080/rs/mobile/system_time?access_token=b69c2f61212780c901e69cebd6854667 +http://localhost:8083/mobile/system_time [GET] + +curl --location 'http://localhost:8083/mobile/system_time' \ +--header 'Authorization: Bearer eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiNDllOTQ0N2E2ZGJlMTUyZTBkMmE2YjYzNDRmZmQyNWQiLCJpYXQiOjE2OTU4NjY1NDAsImV4cCI6MTY5NTkwOTc0MCwiYXVkIjoibW9iaWxlLWNsaWVudCJ9.WjB1lvsccXXBJiOeHMuvo-kZmpfpi1YQgU8NuGYmR70' +response +{ + "time": 1695628213913 +} diff --git "a/others/\351\241\271\347\233\256\345\274\200\345\217\221\350\247\204\350\214\203.txt" "b/others/\351\241\271\347\233\256\345\274\200\345\217\221\350\247\204\350\214\203.txt" index 18f099cb24608adf000a8608456b138eb056923c..a67888894fc37264b63cc73902bc861792844792 100644 --- "a/others/\351\241\271\347\233\256\345\274\200\345\217\221\350\247\204\350\214\203.txt" +++ "b/others/\351\241\271\347\233\256\345\274\200\345\217\221\350\247\204\350\214\203.txt" @@ -6,12 +6,10 @@ >> 1.开发使用软件及版本信息 - * JDK 1.7.0_40 - * Maven 3.1.0 - * IDEA 11.1.3 - * Tomcat 7.0.47 - * MySQL 5.6 - * Git 1.7.10 + * JDK 1.8 + * Maven 3.6.0 + * MySQL 5.6+ + * Git 1.8+ 开发平台: Win 7 diff --git a/pom.xml b/pom.xml index a7c1c15f1f3479e935fbf2564ef3434dbf6af0d3..7d12923f025e338e968217150fd4c67552b90ef0 100644 --- a/pom.xml +++ b/pom.xml @@ -6,13 +6,14 @@ com.monkeyk oauth2-shiro - 0.3 + 2.0.0 ${project.artifactId} pom oltu and shiro + 1.8 UTF-8 @@ -39,8 +40,8 @@ maven-compiler-plugin 3.2 - 1.7 - 1.7 + ${java.version} + ${java.version} UTF-8 @@ -48,8 +49,5 @@ - - - \ No newline at end of file diff --git a/resources/pom.xml b/resources/pom.xml index eddbc5c0e4f2a9c33cb6fa4e7006b12eab9d096a..45bdbcd9ec8680b4d34bb277bebb4d8a7c2a1815 100644 --- a/resources/pom.xml +++ b/resources/pom.xml @@ -4,202 +4,46 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.13 + + + com.monkeyk resources - 0.3 + 2.0.0 ${project.artifactId} - war + jar oltu and shiro [RESOURCES] + 1.8 UTF-8 - 3.2.2.RELEASE - 1.6.12 + + + ${project.version} - 5.1.35 + 1.0.2 - 1.11.0 - 2.9.10.8 + + - - com.mysql.jdbc.Driver - jdbc:mysql://localhost:3306/oauth2_shiro_test?autoReconnect=true&useUnicode=true&characterEncoding=utf8 - andaily - andaily - false - - rs - - - - maven-compiler-plugin - 3.2 - - 1.7 - 1.7 - UTF-8 - - - - - maven-war-plugin - 2.6 - - */classes/resources.properties - */classes/resources.properties - - false - - ${project.version} - monkeyk.com - monkeyk.com - ${project.name} - ${project.version} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - maven-surefire-plugin - 2.4 - - ${test.skip} - none - - **/*Test.java - - - - jdbc.url - ${jdbc.url} - - - jdbc.username - ${jdbc.user} - - - jdbc.password - ${jdbc.pass} - - - - - - - - - src/main/resources - - **/* - - - - - - - - - - src/test/resources - - **/* - - - - com.monkeyk core - 0.3 - - - - - javax.servlet - javax.servlet-api - 3.1.0 - provided - - - javax.servlet.jsp - jsp-api - 2.1 - provided - - - - - org.aspectj - aspectjrt - ${aspectj.version} - compile - - - org.aspectj - aspectjweaver - ${aspectj.version} - compile + ${core.version} - - commons-dbcp - commons-dbcp - 1.4 - - - - commons-io - commons-io - 2.4 - @@ -214,108 +58,76 @@ - - - - - - - - - - org.springframework - spring-aop - ${spring.version} - - - - org.springframework - spring-tx - ${spring.version} + org.springframework.boot + spring-boot-starter-jdbc - org.springframework - spring-expression - ${spring.version} + org.springframework.boot + spring-boot-starter-web - org.springframework - spring-web - ${spring.version} - - - org.springframework - spring-webmvc - ${spring.version} + org.springframework.boot + spring-boot-starter-thymeleaf - mysql mysql-connector-java - ${mysql.version} - compile - - - - - - log4j - log4j - 1.2.14 - compile - - - org.slf4j - slf4j-log4j12 - 1.7.5 - compile + - javax.servlet - jstl - 1.1.2 - - - taglibs - standard - 1.1.2 - compile + org.springframework.boot + spring-boot-starter-test + test + + + rs - - - com.fasterxml.jackson.core - jackson-databind - ${fasterxml.jackson.version} - + + + org.springframework.boot + spring-boot-maven-plugin + + + pl.project13.maven + git-commit-id-plugin + 4.9.10 + + + + revision + + + + + true + true + + ^git.remote.origin.url$ + ^git.branch$ + ^git.commit.id$ + ^git.build.time$ + + + - - - org.springframework - spring-test - ${spring.version} - test - - - org.testng - testng - 6.1.1 - test - - - junit - junit - - - + + maven-surefire-plugin + + + ${test.skip} + none + + + - + \ No newline at end of file diff --git a/resources/src/main/java/com/monkeyk/os/ResourcesApplication.java b/resources/src/main/java/com/monkeyk/os/ResourcesApplication.java new file mode 100644 index 0000000000000000000000000000000000000000..ae8d5e7978b11cd16ea0a6c495d30d921012d32b --- /dev/null +++ b/resources/src/main/java/com/monkeyk/os/ResourcesApplication.java @@ -0,0 +1,18 @@ +package com.monkeyk.os; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * 2020/7/13 + * + * @author Shengzhao Li + * @since 2.0.0 + */ +@SpringBootApplication +public class ResourcesApplication { + + public static void main(String[] args) { + SpringApplication.run(ResourcesApplication.class, args); + } +} diff --git a/resources/src/main/java/com/monkeyk/os/config/RsContextConfig.java b/resources/src/main/java/com/monkeyk/os/config/RsContextConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..ad9d1bc2f177fb766cf34494201916508e552a18 --- /dev/null +++ b/resources/src/main/java/com/monkeyk/os/config/RsContextConfig.java @@ -0,0 +1,43 @@ +package com.monkeyk.os.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.transaction.TransactionManager; + +import javax.sql.DataSource; + +/** + * 2023/9/21 11:30 + *

+ * replaced old rs-context.xml + * + * @author Shengzhao Li + * @since 2.0.0 + */ +@Configuration +public class RsContextConfig { + + + + /** + * 事务配置 + */ + @Bean + public TransactionManager transactionManager(DataSource dataSource) { + DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); + transactionManager.setDataSource(dataSource); + return transactionManager; + } + + + @Bean + public JdbcTemplate jdbcTemplate(DataSource dataSource) { + JdbcTemplate jdbcTemplate = new JdbcTemplate(); + jdbcTemplate.setDataSource(dataSource); + return jdbcTemplate; + } + + +} diff --git a/resources/src/main/java/com/monkeyk/os/config/RsSecurityConfig.java b/resources/src/main/java/com/monkeyk/os/config/RsSecurityConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..80fcb2d54b361d5075718aff517975513a7df30f --- /dev/null +++ b/resources/src/main/java/com/monkeyk/os/config/RsSecurityConfig.java @@ -0,0 +1,150 @@ +package com.monkeyk.os.config; + +import com.monkeyk.os.oauth.shiro.OAuth2Filter; +import com.monkeyk.os.oauth.shiro.OAuth2JdbcRealm; +import com.monkeyk.os.oauth.shiro.OAuth2SubjectFactory; +import com.monkeyk.os.service.OAuthRSService; +import org.apache.shiro.authc.credential.CredentialsMatcher; +import org.apache.shiro.authc.credential.SimpleCredentialsMatcher; +import org.apache.shiro.cache.CacheManager; +import org.apache.shiro.cache.MemoryConstrainedCacheManager; +import org.apache.shiro.mgt.SecurityManager; +import org.apache.shiro.realm.Realm; +import org.apache.shiro.realm.jdbc.JdbcRealm; +import org.apache.shiro.spring.web.ShiroFilterFactoryBean; +import org.apache.shiro.web.mgt.DefaultWebSecurityManager; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.servlet.Filter; +import javax.sql.DataSource; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * 2023/9/21 11:30 + *

+ * replaced old rs-security.xml + *

+ *

+ * RS(资源服务器)的安全配置, 基于SHIRO; + * 这儿主要是对access_token进行消费(即使用access_token), + * 将对access_token 进行效验, 以及通过access_token获取用户相关信息(如果支持) + * + * @author Shengzhao Li + * @since 2.0.0 + */ +@Configuration +public class RsSecurityConfig { + + + @Autowired + private OAuthRSService oAuthRSService; + + /** + * 凭证(access_token)的匹配使用直接比较是否相等 + */ + @Bean + public CredentialsMatcher credentialsMatcher() { + return new SimpleCredentialsMatcher(); + } + + + @Bean + public JdbcRealm jdbcRealm(DataSource dataSource) { + OAuth2JdbcRealm jdbcRealm = new OAuth2JdbcRealm(); + jdbcRealm.setName("jdbcRealm"); + jdbcRealm.setDataSource(dataSource); + jdbcRealm.setCredentialsMatcher(credentialsMatcher()); + jdbcRealm.setPermissionsLookupEnabled(true); + jdbcRealm.setRsService(oAuthRSService); + return jdbcRealm; + } + + + /** + * 若在集群环境下,推荐使用如 redis 之类支持集群的缓存实现 + */ + @Bean + public CacheManager shiroCacheManager() { + return new MemoryConstrainedCacheManager(); + } + + @Bean + public OAuth2SubjectFactory subjectFactory() { + return new OAuth2SubjectFactory(); + } + + @Bean + public DefaultWebSecurityManager securityManager(Realm realm) { + DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); + securityManager.setRealm(realm); + securityManager.setCacheManager(shiroCacheManager()); + securityManager.setSubjectFactory(subjectFactory()); + return securityManager; + } + + /** + * 对于每一个对外提供的Resource(资源), 都需要在此定义, 每个资源对应不同的URL pattern. + * 且有唯一的 resource id, 具体的拦截处理见 OAuth2Filter.java 类 + * 可根据实际需要 进行扩展 + *

+ * Single resource + *

+ * resourceId 在新的OAuth2实现中是可选的了 @since 2.0.0 + * 注意:不配置为bean + */ + OAuth2Filter auth2Filter() { + OAuth2Filter oAuth2Filter = new OAuth2Filter(); + oAuth2Filter.setResourceId("os-resource"); + oAuth2Filter.setRsService(oAuthRSService); + return oAuth2Filter; + } + + /** + * mobile resource + * 注意:不配置为bean + */ + OAuth2Filter mobileOauth2Filter() { + OAuth2Filter oAuth2Filter = new OAuth2Filter(); + oAuth2Filter.setResourceId("mobile-resource"); + oAuth2Filter.setRsService(oAuthRSService); + return oAuth2Filter; + } + + + /** + * SHIRO 核心配置 拦截器(或过滤器) + * 扩展添加自定义的 Resource Filter(如 oauth2Filter) 用于实现对 access_token的校验与处理 + * 在filterChainDefinitions的配置中增加对具体 URL pattern的 OAUTH 保护拦截 + * 可根据实际需求进行扩展 + */ + @Bean + public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { + ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); + bean.setSecurityManager(securityManager); + bean.setLoginUrl("/login"); + bean.setSuccessUrl("/index"); + bean.setUnauthorizedUrl("/unauthorized"); + + Map filters = new LinkedHashMap<>(); + filters.put("oauth", auth2Filter()); + filters.put("mOauth", mobileOauth2Filter()); +// filters.put("oauth2",xxxOauth2Filter()); +// filters.put("oauth3",yyyyyOauth2Filter()); + bean.setFilters(filters); + + Map filterChainDefinitionMap = new LinkedHashMap<>(); + //#oauth + filterChainDefinitionMap.put("/rs/**", "oauth"); + filterChainDefinitionMap.put("/mobile/**", "mOauth"); + // # everything else allow + filterChainDefinitionMap.put("/**", "anon"); + bean.setFilterChainDefinitionMap(filterChainDefinitionMap); + + return bean; + } + + +} diff --git a/resources/src/main/java/com/monkeyk/os/config/RsWebConfig.java b/resources/src/main/java/com/monkeyk/os/config/RsWebConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..83919fa54dc891b9fec48701520d62489c951e93 --- /dev/null +++ b/resources/src/main/java/com/monkeyk/os/config/RsWebConfig.java @@ -0,0 +1,75 @@ +package com.monkeyk.os.config; + +import com.monkeyk.os.web.context.MkkCharacterEncodingFilter; +import com.monkeyk.os.web.context.OAuthShiroHandlerExceptionResolver; +import org.apache.shiro.mgt.SecurityManager; +import org.apache.shiro.spring.LifecycleBeanPostProcessor; +import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.HandlerExceptionResolver; + +/** + * 2023/9/21 12:01 + *

+ * replaced web.xml, mkk-servlet.xml + * + * @author Shengzhao Li + * @since 2.0.0 + */ +@Configuration +public class RsWebConfig { + + + /** + * 字符编码配置 UTF-8 + */ + @Bean + public FilterRegistrationBean encodingFilter() { + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + registrationBean.setFilter(new MkkCharacterEncodingFilter()); + registrationBean.addUrlPatterns("/*"); + //值越小越靠前 + registrationBean.setOrder(1); + return registrationBean; + } + + + /** + * 异常处理配置 + */ + @Bean + public HandlerExceptionResolver handlerExceptionResolver() { + return new OAuthShiroHandlerExceptionResolver(); + } + + + /** + * Shiro AOP + */ + @Bean + public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { + return new LifecycleBeanPostProcessor(); + } + + /** + * Enable Shiro Annotations for Spring-configured beans. Only run after + * the lifecycleBeanProcessor has run: + */ +// public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){ +// DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator(); +// autoProxyCre +// return autoProxyCreator; +// } + + + @Bean + public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { + AuthorizationAttributeSourceAdvisor sourceAdvisor = new AuthorizationAttributeSourceAdvisor(); + sourceAdvisor.setSecurityManager(securityManager); + return sourceAdvisor; + } + + +} diff --git a/resources/src/main/java/com/monkeyk/os/oauth/resources/ResourceOAuthFilter.java b/resources/src/main/java/com/monkeyk/os/oauth/resources/ResourceOAuthFilter.java index 65c8550920cb9b02ea36f438c7cf071ce254ef7f..a77359c5b2f77c886660e5f26ad32c723fd51bf4 100644 --- a/resources/src/main/java/com/monkeyk/os/oauth/resources/ResourceOAuthFilter.java +++ b/resources/src/main/java/com/monkeyk/os/oauth/resources/ResourceOAuthFilter.java @@ -11,6 +11,7 @@ import org.apache.oltu.oauth2.rs.response.OAuthRSResponse; import org.apache.oltu.oauth2.rsfilter.OAuthDecision; import org.apache.oltu.oauth2.rsfilter.OAuthRSProvider; import org.apache.oltu.oauth2.rsfilter.OAuthUtils; +import org.apache.shiro.web.util.WebUtils; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; @@ -90,8 +91,8 @@ public class ResourceOAuthFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - HttpServletRequest req = (HttpServletRequest) request; - HttpServletResponse res = (HttpServletResponse) response; + HttpServletRequest req = WebUtils.toHttp(request); + HttpServletResponse res = WebUtils.toHttp(response); try { @@ -137,7 +138,7 @@ public class ResourceOAuthFilter implements Filter { //nothing } - /* + /** * 默认的 ERROR 返回信息在header中, * 可根据实际需要修改为JSON或XML数据格式返回 */ diff --git a/resources/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2Filter.java b/resources/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2Filter.java index 500d77c3d0c06583fd0b63bbfe601bd58176d3fd..442af6d3964d47558b39a75de8bc0fe8be9ec971 100644 --- a/resources/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2Filter.java +++ b/resources/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2Filter.java @@ -9,7 +9,9 @@ import org.apache.oltu.oauth2.common.message.OAuthResponse; import org.apache.oltu.oauth2.rs.response.OAuthRSResponse; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationToken; -import org.apache.shiro.web.filter.authc.AuthenticatingFilter; +import org.apache.shiro.authc.BearerToken; +import org.apache.shiro.web.filter.authc.BearerHttpAuthenticationFilter; +import org.apache.shiro.web.util.WebUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; @@ -25,26 +27,31 @@ import javax.servlet.http.HttpServletResponse; *

* 对需要保护的资源进行拦截过滤处理 * 需要与SHIRO的安全整合并加入到SHIRO 流程中 - * 相关配置见 rs-security.xml 文件 + * 相关配置见 RsSecurityConfig.java * * @author Shengzhao Li + * @see com.monkeyk.os.oauth.resources.ResourceOAuthFilter */ -public class OAuth2Filter extends AuthenticatingFilter implements InitializingBean { +public class OAuth2Filter extends BearerHttpAuthenticationFilter implements InitializingBean { private static final Logger LOGGER = LoggerFactory.getLogger(OAuth2Filter.class); - private String resourceId; + /** + * resourceId 是可选的,可忽略, since 2.0.0 + */ + private String resourceId = "default"; private OAuthRSService rsService; @Override - protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception { + protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) { - HttpServletRequest httpRequest = (HttpServletRequest) request; + HttpServletRequest httpRequest = WebUtils.toHttp(request); - final String accessToken = getAccessToken(httpRequest); + BearerToken bearerToken = (BearerToken) super.createToken(request, response); + final String accessToken = bearerToken.getToken(); final AccessToken token = rsService.loadAccessTokenByTokenId(accessToken); String username = null; @@ -60,16 +67,8 @@ public class OAuth2Filter extends AuthenticatingFilter implements InitializingBe .setUserId(username); } - private String getAccessToken(HttpServletRequest httpRequest) { - final String authorization = httpRequest.getHeader("Authorization"); - if (authorization != null) { - //bearer ab1ade69-d122-4844-ab23-7b109ad977f0 - return authorization.substring(6).trim(); - } - return httpRequest.getParameter(OAuth.OAUTH_ACCESS_TOKEN); - } - + @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { return false; } @@ -113,5 +112,9 @@ public class OAuth2Filter extends AuthenticatingFilter implements InitializingBe public void afterPropertiesSet() throws Exception { Assert.notNull(resourceId, "resourceId is null"); Assert.notNull(rsService, "rsService is null"); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Initialized: {} -> resourceId: {}, rsService: {}", this, resourceId, rsService); + } } } diff --git a/resources/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2JdbcRealm.java b/resources/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2JdbcRealm.java index 85c6c0bbb45b53b72a9ec7b23514aa95fe7d86af..70bd88aa176993ecb81a29b3e7f69699f84ad40d 100644 --- a/resources/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2JdbcRealm.java +++ b/resources/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2JdbcRealm.java @@ -65,7 +65,7 @@ public class OAuth2JdbcRealm extends MkkJdbcRealm { OAuth2Token upToken = (OAuth2Token) token; final String accessToken = (String) upToken.getCredentials(); - if (StringUtils.isEmpty(accessToken)) { + if (!StringUtils.hasLength(accessToken)) { throw new OAuth2AuthenticationException("Invalid access_token: " + accessToken); } //Validate access token @@ -135,6 +135,6 @@ public class OAuth2JdbcRealm extends MkkJdbcRealm { public void afterPropertiesSet() throws Exception { super.afterPropertiesSet(); - Assert.notNull(this.rsService); + Assert.notNull(this.rsService, "rsService is required"); } } diff --git a/resources/src/main/java/com/monkeyk/os/web/context/BeanContextAware.java b/resources/src/main/java/com/monkeyk/os/web/context/BeanContextAware.java new file mode 100644 index 0000000000000000000000000000000000000000..f3f3b23bb127a86b8b4e7352b92d5f0af1278fa7 --- /dev/null +++ b/resources/src/main/java/com/monkeyk/os/web/context/BeanContextAware.java @@ -0,0 +1,33 @@ +package com.monkeyk.os.web.context; + +import com.monkeyk.os.domain.shared.BeanProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +import static com.monkeyk.os.domain.oauth.Constants.VERSION; + +/** + * 2020/7/14 + * + * @author Shengzhao Li + * @since 2.0.0 + */ +@Component +public class BeanContextAware implements ApplicationContextAware { + + private static final Logger LOG = LoggerFactory.getLogger(BeanContextAware.class); + + + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + BeanProvider.initialize(applicationContext); + if (LOG.isInfoEnabled()) { + LOG.info("Deploy [resources] version: {}", VERSION); + } + } +} diff --git a/resources/src/main/java/com/monkeyk/os/web/context/BeanContextLoaderListener.java b/resources/src/main/java/com/monkeyk/os/web/context/BeanContextLoaderListener.java index c59a2ee82eb056a1545e3455a1220e6856b316f7..4d30d8b7395328d01e66586b4f29d43b9b147ab7 100644 --- a/resources/src/main/java/com/monkeyk/os/web/context/BeanContextLoaderListener.java +++ b/resources/src/main/java/com/monkeyk/os/web/context/BeanContextLoaderListener.java @@ -9,7 +9,9 @@ import javax.servlet.ServletContextEvent; /** * @author Shengzhao Li + * @deprecated Use BeanContextAware.java replaced from 2.0.0 */ +@Deprecated public class BeanContextLoaderListener extends ContextLoaderListener { diff --git a/resources/src/main/java/com/monkeyk/os/web/controller/WelcomeController.java b/resources/src/main/java/com/monkeyk/os/web/controller/WelcomeController.java new file mode 100644 index 0000000000000000000000000000000000000000..a6699e4374a793304d7bac727baaca87eed419a1 --- /dev/null +++ b/resources/src/main/java/com/monkeyk/os/web/controller/WelcomeController.java @@ -0,0 +1,25 @@ +package com.monkeyk.os.web.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; + +import static com.monkeyk.os.domain.oauth.Constants.VERSION; + +/** + * 2023/9/21 12:22 + * + * @author Shengzhao Li + * @since 2.0.0 + */ +@Controller +public class WelcomeController { + + + @GetMapping("/") + public String welcome(Model model) { + model.addAttribute("version", VERSION); + return "welcome"; + } + +} diff --git a/resources/src/main/resources/application.properties b/resources/src/main/resources/application.properties new file mode 100644 index 0000000000000000000000000000000000000000..a409a9a5697e55419471c918f16e60c056f60d1a --- /dev/null +++ b/resources/src/main/resources/application.properties @@ -0,0 +1,18 @@ +# +spring.application.name=Resources +# +server.port=8083 +# +#JDBC configuration information +# connect the same db with authz +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.datasource.url=jdbc:mysql://localhost:3306/oauth2_shiro?autoReconnect=true&autoReconnectForPools=true&useUnicode=true&characterEncoding=utf8&useSSL=false +spring.datasource.username=andaily +spring.datasource.password=andaily +#Datasource properties +spring.datasource.type=com.zaxxer.hikari.HikariDataSource +spring.datasource.hikari.maximum-pool-size=20 +# +#Override default +spring.main.allow-bean-definition-overriding=true +# diff --git a/resources/src/main/resources/banner.txt b/resources/src/main/resources/banner.txt new file mode 100644 index 0000000000000000000000000000000000000000..0bdbaaabd7276af05faaa9a01df5d9220569a51e --- /dev/null +++ b/resources/src/main/resources/banner.txt @@ -0,0 +1,10 @@ +${AnsiColor.BRIGHT_GREEN} + ____ _ _____ _ ____ +/ _ \/ \ /\/__ __\/ \ /|/_ \ +| / \|| | || / \ | |_|| / / +| |-||| \_/| | | | | ||/ /_ +\_/ \|\____/ \_/ \_/ \|\____/ + + +RS: ${application.formatted-version} +Spring Boot: ${spring-boot.formatted-version} diff --git a/resources/src/main/resources/logback.xml b/resources/src/main/resources/logback.xml new file mode 100644 index 0000000000000000000000000000000000000000..3766fc8d91e14b129b9cff8a10fa747c13c172a0 --- /dev/null +++ b/resources/src/main/resources/logback.xml @@ -0,0 +1,45 @@ + + + ${spring.application.name} + + + + + + %d{yyyy-MM-dd HH:mm:ss} [%-5level] [%.80c{10}][%L] -%m%n + + + + + + + true + + + logs/%d{yyyy-MM-dd}/rs-%i.log + 10MB + 15 + + + + + %d{yyyy-MM-dd HH:mm:ss} [%-5level] [%.80c{10}][%L] -%m%n + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/src/main/resources/logging.properties b/resources/src/main/resources/logging.properties deleted file mode 100644 index 1d66b3e2b32eb6debe0068af283200d3092ae229..0000000000000000000000000000000000000000 --- a/resources/src/main/resources/logging.properties +++ /dev/null @@ -1,14 +0,0 @@ -handlers = org.apache.juli.FileHandler, java.util.logging.ConsoleHandler - -############################################################ -# Handler specific properties. -# Describes specific configuration info for Handlers. -# The configuration for Tomcat server -############################################################ - -org.apache.juli.FileHandler.level = FINE -org.apache.juli.FileHandler.directory = ${catalina.base}/logs -org.apache.juli.FileHandler.prefix = error-debug. - -java.util.logging.ConsoleHandler.level = FINE -java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter diff --git a/resources/src/main/resources/resources.properties b/resources/src/main/resources/resources.properties deleted file mode 100644 index 9280944fb4a5de3f98a3a91293d97a763c210910..0000000000000000000000000000000000000000 --- a/resources/src/main/resources/resources.properties +++ /dev/null @@ -1,14 +0,0 @@ - -# -# ļ, õݿϢҪ authz ģһ -# authz ģ access_token , resources ģaccess_token -# -#JDBC configuration information -jdbc.driverClassName=com.mysql.jdbc.Driver -############ -# MySQL DB -############ -jdbc.url=jdbc:mysql://localhost:3306/oauth2_shiro?autoReconnect=true&autoReconnectForPools=true&useUnicode=true&characterEncoding=utf8 -jdbc.username=andaily -jdbc.password=andaily - diff --git a/resources/src/main/resources/spring/rs-context.xml b/resources/src/main/resources/spring/rs-context.xml deleted file mode 100644 index bb96fe6f18ce00cbce3fac9e7b0c93dee9587792..0000000000000000000000000000000000000000 --- a/resources/src/main/resources/spring/rs-context.xml +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - - - - - - - - classpath:resources.properties - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/resources/src/main/resources/spring/rs-security.xml b/resources/src/main/resources/spring/rs-security.xml deleted file mode 100644 index cc9103c5bd7a6febe11a3ecaa30622b5c6c9c85a..0000000000000000000000000000000000000000 --- a/resources/src/main/resources/spring/rs-security.xml +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # oauth - /rs/** = oauth - /mobile/** = mOauth - # everything else allow - /** = anon - - - - - - \ No newline at end of file diff --git a/resources/src/main/webapp/static/bootstrap.min.css b/resources/src/main/resources/static/css/bootstrap.min.css similarity index 100% rename from resources/src/main/webapp/static/bootstrap.min.css rename to resources/src/main/resources/static/css/bootstrap.min.css diff --git a/resources/src/main/webapp/favicon.ico b/resources/src/main/resources/static/favicon.ico similarity index 100% rename from resources/src/main/webapp/favicon.ico rename to resources/src/main/resources/static/favicon.ico diff --git a/authz/src/main/webapp/resources/OS_API-0.2.html b/resources/src/main/resources/static/html/OS_API-0.2.html similarity index 99% rename from authz/src/main/webapp/resources/OS_API-0.2.html rename to resources/src/main/resources/static/html/OS_API-0.2.html index bbd9fbd6a39271534007c001e5c5487a92daef12..3b8cfd59dd660c31b6929366b06c58008fe9eeab 100644 --- a/authz/src/main/webapp/resources/OS_API-0.2.html +++ b/resources/src/main/resources/static/html/OS_API-0.2.html @@ -4,7 +4,7 @@ - + oauth2-shiro API + + +

+ +
+ 说明: 本文档用于描述oauth2-shiro对外开发的接口(API)使用,分为 authz 与 resources 两个部分, 所有标记 + public + 的API都是公开的, 其他的API则需要获取 + access_token + 后可调用 +
+ +
+ +
+ +
+

[authz]

+ +

获取access_token (grant_type=password) + public +

+ +

使用grant_type=password方式来获取access_token

+ +
    +
  • +

    + 请求URI: /oauth/token POST +

    + +
    + 请求参数说明: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数名参数值必须?备注
    client_id{client_id}
    client_secret{client_secret}
    grant_typepassword固定值
    scope{scope}read or write
    username{username}用户名
    password{password}用户密码
    + 请求示例: +
    http://localhost:8080/oauth/token?client_id=test-client&client_secret=Test@2015$$&grant_type=password&scope=read&username=test&password=Test@2015#
    +
    curl --location 'http://localhost:8080/oauth/token' \
    +--header 'Content-Type: application/x-www-form-urlencoded' \
    +--data-urlencode 'client_id=mobile-client' \
    +--data-urlencode 'client_secret=Mobile@2015$$' \
    +--data-urlencode 'grant_type=password' \
    +--data-urlencode 'username=test' \
    +--data-urlencode 'password=Test@2015#' \
    +--data-urlencode 'scope=read'
    + +
    +
    + + 响应 + +
      +
    • +
      + 正常 [200]
      +
      {
      +    "access_token": "eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiNDllOTQ0N2E2ZGJlMTUyZTBkMmE2YjYzNDRmZmQyNWQiLCJpYXQiOjE2OTU4NjY1NDAsImV4cCI6MTY5NTkwOTc0MCwiYXVkIjoibW9iaWxlLWNsaWVudCJ9.WjB1lvsccXXBJiOeHMuvo-kZmpfpi1YQgU8NuGYmR70",
      +    "refresh_token": "46a2017568aee3875a42f7c2234f4b3d",
      +    "token_type": "Bearer",
      +    "expires_in": 43199
      +}
      +
      +
    • +
    • +
      + 异常 [400]
      +
      {"error":"invalid_grant","error_description":"Bad credentials"}
      +
      +
    • +
    +
  • +
+
+ +
+

[authz]

+ +

获取access_token (grant_type=authorization_code) + public +

+ +

使用grant_type=authorization_code 方式来获取access_token, 需要先获取code

+ +
    +
  • +

    + 请求URI: /oauth/token POST +

    + +
    + 请求参数说明: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数名参数值必须?备注
    client_id{client_id}
    client_secret{client_secret}
    grant_typeauthorization_code固定值
    code{code}
    redirect_uri{redirect_uri}
    + 请求示例: +
    http://localhost:8080/oauth/token?client_id=test-client&client_secret=Test@2015$$&grant_type=authorization_code&code=ac0bd18863b07adfb518cc6e6dfcfcab&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fspring-oauth-client%2Fauthorization_code_callback
    +
    curl --location 'http://localhost:8080/oauth/token' \
    +--header 'Content-Type: application/x-www-form-urlencoded' \
    +--data-urlencode 'client_id=test-client' \
    +--data-urlencode 'client_secret=Test@2015$$' \
    +--data-urlencode 'grant_type=authorization_code' \
    +--data-urlencode 'redirect_uri=http://localhost:7777/spring-oauth-client/authorization_code_callback' \
    +--data-urlencode 'code=52aa9d9cb8e62649e887e745fda94fa7'
    +
    +
    + + 响应 + +
      +
    • +
      + 正常 [200]
      +
      {
      +    "access_token": "eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiY2I2ZmIzYTFmNzA0OGU3NDYxZjcwYWI2OGNkYTk1ZjUiLCJpYXQiOjE2OTU4NjYzODIsImV4cCI6MTY5NTkwOTU4MiwiYXVkIjoidGVzdC1jbGllbnQifQ.NqJe-j7p3UC2gJlBJ-tKB4GrFsW9OR-GyxMfm4LIfwQ",
      +    "refresh_token": "019b043ddcf5994220617b6795c5216a",
      +    "token_type": "Bearer",
      +    "expires_in": 43199
      +}
      +
      +
    • +
    • +
      + 异常 [400]
      +
      {"error":"invalid_grant","error_description":"Invalid code '26964e42c667b5d42f89a1255766630a'"}
      +
      +
    • +
    +
  • +
+
+ +
+

[authz]

+ +

获取access_token (grant_type=token) + public +

+ +

使用grant_type=token 方式来获取access_token, implicit模式; 需要登录

+ +
    +
  • +

    + 请求URI: /oauth/token GET [deprecated] +

    + +
    + 请求参数说明: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数名参数值必须?备注
    client_id{client_id}
    grant_typetoken固定值
    scope{scope}read or write
    redirect_uri{redirect_uri}
    + 请求示例: +
    http://localhost:8080/oauth/authorize?response_type=token&scope=read write&client_id=test-client&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fspring-oauth-client%2Fauthorization_code_callback
    + +
    +
    + + 响应 + +
      +
    • +
      + 正常 [200]
      +
      http://localhost:7777/spring-oauth-client/authorization_code_callback#access_token=eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiZmI2YWM1M2E5YjRlMjFkMjcyMmU4Y2FjMDBjODkyNGUiLCJpYXQiOjE2OTU4Njc3MjMsImV4cCI6MTY5NTkxMDkyMywiYXVkIjoidGVzdC1jbGllbnQifQ.i7WyVE_08DeKeq_SpI-C2sqTaKDXt-wKck1L_L_aW98&token_type=Bearer&expires_in=43199
      +
      +
    • +
    +

    通过 redirect_uri的 URL hash 传递access_token信息

    +
  • +
+
+ +
+

[authz]

+ +

获取access_token (grant_type=client_credentials) + public +

+ +

使用grant_type=client_credentials 方式来获取access_token, 不需要username, password, 不支持 + refresh_token

+ +
    +
  • +

    + 请求URI: /oauth/token POST +

    + +
    + 请求参数说明: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数名参数值必须?备注
    client_id{client_id}
    client_secret{client_secret}
    grant_typeclient_credentials固定值
    scope{scope}read or write
    + 请求示例: +
    http://localhost:8080/oauth/token?client_id=test-client&client_secret=Test@2015$$&grant_type=client_credentials&scope=read
    + +
    curl --location 'http://localhost:8080/oauth/token' \
    +--header 'Content-Type: application/x-www-form-urlencoded' \
    +--data-urlencode 'client_id=test-client' \
    +--data-urlencode 'client_secret=Test@2015$$' \
    +--data-urlencode 'grant_type=client_credentials' \
    +--data-urlencode 'scope=read'
    +
    +
    + + 响应 + +
      +
    • +
      + 正常 [200]
      +
      {
      +    "access_token": "eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0LWNsaWVudCIsImp0aSI6IjlkNTZhMjFhYzNhZGMzMWQyYzRjZDJlOWEyNTNkY2RmIiwiaWF0IjoxNjk1ODY2NjA5LCJleHAiOjE2OTU5MDk4MDksImF1ZCI6InRlc3QtY2xpZW50In0.brapFTd_HiPfrlKZWOK9MXOFKrDRD7v2dqXnGU7nkjI",
      +    "token_type": "Bearer",
      +    "expires_in": 43199
      +}
      +
      +
    • +
    • +
      + 异常 [401]
      +
      {"error":"invalid_client","error_description":"Invalid client_id'test-xxx'"}
      +
      +
    • +
    +
  • +
+
+ +
+

[authz]

+ +

刷新access_token (grant_type=refresh_token) + public +

+ +

用于在access_token要过期时换取新的access_token (grant_type需要有refresh_token)

+ +
    +
  • +

    + 请求URI: /oauth/token POST +

    + +
    + 请求参数说明: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数名参数值必须?备注
    client_id{client_id}
    client_secret{client_secret}
    grant_typerefresh_token固定值
    refresh_token{refresh_token}
    + 请求示例: +
    http://localhost:8080/oauth/token?client_id=test-client&client_secret=Test@2015$$&grant_type=refresh_token&refresh_token=b36f4978a1724aa8af8960f58abe3ba1
    + +
    curl --location 'http://localhost:8080/oauth/token' \
    +--header 'Content-Type: application/x-www-form-urlencoded' \
    +--data-urlencode 'client_id=test-client' \
    +--data-urlencode 'client_secret=Test@2015$$' \
    +--data-urlencode 'grant_type=refresh_token' \
    +--data-urlencode 'refresh_token=8c46797a0101800626270ce6579c84fa'
    +
    +
    + + 响应 + +
      +
    • +
      + 正常 [200]
      +
      {
      +    "access_token": "eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiZTUyYTExZGU4Y2EwNGUwOTI1Y2RhNDMxNDYwN2NmZGUiLCJpYXQiOjE2OTU4NjY2NzksImV4cCI6MTY5NTkwOTg3OSwiYXVkIjoidGVzdC1jbGllbnQifQ.qLkvnAartpxkiFfeMwnzrK61ihJtXAu6ml5tFU8O-NU",
      +    "refresh_token": "bb277d6ba38bbf5d6facae92eb29e286",
      +    "token_type": "Bearer",
      +    "expires_in": 43199
      +}
      +
      +
    • +
    • +
      + 异常 [400]
      +
      {"error":"invalid_grant","error_description":"Invalid refresh_token: 8e91a56f53857688a3ffd8c7cfd311cfss"}
      +
      +
    • +
    +
  • +
+
+ +
+ +
+

[resources]

+ +

获取当前系统时间(resource-id: mobile-resource)

+ +

获取当前系统时间, 需要access_token的 resource-id 为 mobile-resource 才能访问

+ +
    +
  • +

    + 请求URI: /mobile/system_time GET +

    + +
    + 请求参数说明: + + + + + + + + + + + + + + +
    参数名参数值必须?备注
    + 请求示例: +
    http://localhost:8083/mobile/system_time?access_token=eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiNDllOT...
    + +
    curl --location 'http://localhost:8083/mobile/system_time' \
    +--header 'Authorization: Bearer eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiNDllOTQ0N2E2ZGJlMTUyZTBkMmE2YjYzNDRmZmQyNWQiLCJpYXQiOjE2OTU4NjY1NDAsImV4cCI6MTY5NTkwOTc0MCwiYXVkIjoibW9iaWxlLWNsaWVudCJ9.WjB1lvsccXXBJiOeHMuvo-kZmpfpi1YQgU8NuGYmR70'
    +
    + +
    +
    + + 响应 + +
      +
    • +
      + 正常 [200]
      +
      {
      +    "time": 1695628213913
      +}
      +
      +
    • +
    • +
      + 异常 [401]
      +
      {"error":"invalid_token","error_description":"Invalid access_token: 95c3afd44c5d87301dc3034b20b3fc75s"}
      +
      +
    • +
    +
  • +
+
+ +
+

[resources]

+ +

获取当前用户信息 (resource-id: os-resource; Role: User)

+ +

使用access_token获取用户信息, 需要access_token的 resource-id 为 os-resource 且用户Role包含 User + 才能访问

+ +
    +
  • +

    + 请求URI: /rs/username GET +

    + +
    + 请求参数说明: + + + + + + + + + + + + + + +
    参数名参数值必须?备注
    + 请求示例: +
    http://localhost:8083/rs/username?access_token=eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIi...
    + +
    curl --location 'http://localhost:8083/rs/username' \
    +--header 'Authorization: Bearer eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiZTUyYTExZGU4Y2EwNGUwOTI1Y2RhNDMxNDYwN2NmZGUiLCJpYXQiOjE2OTU4NjY2NzksImV4cCI6MTY5NTkwOTg3OSwiYXVkIjoidGVzdC1jbGllbnQifQ.qLkvnAartpxkiFfeMwnzrK61ihJtXAu6ml5tFU8O-NU'
    +
    + +
    +
    + + 响应 + +
      +
    • +
      + 正常 [200]
      +
      {
      +    "clientId": "test-client",
      +    "username": "test"
      +}
      +
      +
    • +
    • +
      + 异常 [401]
      +
      {"error":"invalid_token","error_description":"Invalid client by token: 95c3afd44c5d87301dc3034b20b3fc75"}
      +
      +
    • +
    +
  • +
+
+ +
+
+ + +
+
+
+
+ © 2015-2023 oauth2-shiro +
+
+
+ + \ No newline at end of file diff --git a/resources/src/main/resources/templates/fragments/main.html b/resources/src/main/resources/templates/fragments/main.html new file mode 100644 index 0000000000000000000000000000000000000000..183ad85c5d7e58ce164b53fef99c181f32a5c9bd --- /dev/null +++ b/resources/src/main/resources/templates/fragments/main.html @@ -0,0 +1,32 @@ + + + + + + Fragments + +
+ + +
+ + + + + + + + \ No newline at end of file diff --git a/resources/src/main/webapp/loading.jsp b/resources/src/main/resources/templates/welcome.html similarity index 52% rename from resources/src/main/webapp/loading.jsp rename to resources/src/main/resources/templates/welcome.html index 694154f0791f820501d9e5588cbaefb2bbce4998..4778fa3042eb04ecd799d4d904c8b3cd40579f67 100644 --- a/resources/src/main/webapp/loading.jsp +++ b/resources/src/main/resources/templates/welcome.html @@ -1,35 +1,20 @@ -<%-- - * - * @author Shengzhao Li ---%> - -<%@ page contentType="text/html;charset=UTF-8" language="java" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> - - + + - - - + OAuth2-Shiro[resources] - - + -
@@ -45,38 +30,31 @@

resources 模块提供了两个API接口,需要不同权限的access_token才可以访问, API分别为:

- a. /mobile/system_time -- 获取系统时间 + a. /mobile/system_time -- 获取系统时间

- b. /rs/username -- 获取用户信息 + b. /rs/username -- 获取用户信息

- 对API的详细使用请查看文档 OS_API + 对API的详细使用请查看文档 OS_API

  • -

    对于每一类资源(resource), 需要定义一个 resource-id, 并且需要在 rs-security.xml 中配置一个OAuth2Filter, 然后在 - shiroFilter 中添加该 Filter与配置URL Pattern; 详细请参数项目中 rs-security.xml 文件的配置

    +

    对于每一类资源(resource), 需要定义一个 resource-id, 并且需要在 RsSecurityConfig.java 中配置一个OAuth2Filter, 然后在 + shiroFilter 中添加该 Filter与配置URL Pattern; 详细请参数项目中 RsSecurityConfig.java 的配置

  • - 注意 [resources]模块的配置文件 resources.properties(位于src/main/resources目录) + 注意 [resources]模块的配置文件 application.properties(位于src/main/resources目录) 中数据库连接配置信息必须与 [authz]模块中的配置一致. 因为[authz]模块获取token后存入数据库, [resources]模块在调用时从数据库中查询获取.

  • -
    -
    -
    -
    - © oauth2-shiro -
    -
    -
    +
    \ No newline at end of file diff --git a/resources/src/main/webapp/WEB-INF/log4j.xml b/resources/src/main/webapp/WEB-INF/log4j.xml deleted file mode 100644 index 4ce36d70ba5d783bc6c71f64091eb0e7c7ee5200..0000000000000000000000000000000000000000 --- a/resources/src/main/webapp/WEB-INF/log4j.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/resources/src/main/webapp/WEB-INF/mkk-servlet.xml b/resources/src/main/webapp/WEB-INF/mkk-servlet.xml deleted file mode 100644 index acf290a3c60e79a3de3189297ea5880da9e75d88..0000000000000000000000000000000000000000 --- a/resources/src/main/webapp/WEB-INF/mkk-servlet.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/resources/src/main/webapp/WEB-INF/web.xml b/resources/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 2bbe0d0047f6f799896a6663ffc1f18225b3618f..0000000000000000000000000000000000000000 --- a/resources/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,104 +0,0 @@ - - - - resources - - - - webAppRootKey - resources - - - - - encodingFilter - com.monkeyk.os.web.context.MkkCharacterEncodingFilter - - encoding - UTF-8 - - - forceEncoding - true - - - - encodingFilter - /* - - - - - - - shiroFilter - org.springframework.web.filter.DelegatingFilterProxy - - targetFilterLifecycle - true - - - - - shiroFilter - /* - - - - - ico - image/vnd.microsoft.icon - - - - - contextConfigLocation - classpath:spring/*.xml - - - - log4jConfigLocation - /WEB-INF/log4j.xml - - - org.springframework.web.util.Log4jConfigListener - - - - - - com.monkeyk.os.web.context.BeanContextLoaderListener - - - - - mkk - org.springframework.web.servlet.DispatcherServlet - 2 - - - mkk - / - - - - - - - - - - 30 - - - - - loading.jsp - - - - \ No newline at end of file diff --git a/resources/src/test/java/com/monkeyk/os/ContextTest.java b/resources/src/test/java/com/monkeyk/os/ContextTest.java index 8a6ac84a439f2b7e629232d65e2d52d0d5fd6fce..1be068c3102364e891929a5b10959a727d6c13ea 100644 --- a/resources/src/test/java/com/monkeyk/os/ContextTest.java +++ b/resources/src/test/java/com/monkeyk/os/ContextTest.java @@ -1,15 +1,17 @@ package com.monkeyk.os; import com.monkeyk.os.domain.shared.BeanProvider; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests; import org.springframework.test.context.transaction.BeforeTransaction; /** * @author Shengzhao Li */ -@ContextConfiguration(locations = {"classpath:/spring/*.xml"}, initializers = {TestApplicationContextInitializer.class}) -public abstract class ContextTest extends AbstractTransactionalTestNGSpringContextTests { +@SpringBootTest +@ActiveProfiles("test") +public abstract class ContextTest extends AbstractTransactionalJUnit4SpringContextTests { @BeforeTransaction diff --git a/resources/src/test/java/com/monkeyk/os/ResourcesApplicationTest.java b/resources/src/test/java/com/monkeyk/os/ResourcesApplicationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..f67727242450e8b54561a8c8a0fd58022a1b70e6 --- /dev/null +++ b/resources/src/test/java/com/monkeyk/os/ResourcesApplicationTest.java @@ -0,0 +1,23 @@ +package com.monkeyk.os; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 2020/7/13 + * + * @author Shengzhao Li + * @since 2.0.0 + */ +@SpringBootTest +class ResourcesApplicationTest { + + + @Test + void contextLoads() { + } + + +} \ No newline at end of file diff --git a/resources/src/test/java/com/monkeyk/os/TestApplicationContextInitializer.java b/resources/src/test/java/com/monkeyk/os/TestApplicationContextInitializer.java deleted file mode 100644 index e884f289fe55fca2b40c69eb2c45abf2895bbeb8..0000000000000000000000000000000000000000 --- a/resources/src/test/java/com/monkeyk/os/TestApplicationContextInitializer.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.monkeyk.os; - -import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer; -import org.springframework.context.ApplicationContextInitializer; -import org.springframework.context.support.AbstractApplicationContext; -import org.springframework.core.io.ClassPathResource; - -/** - * @author Shengzhao Li - */ -public class TestApplicationContextInitializer implements ApplicationContextInitializer { - - @Override - public void initialize(AbstractApplicationContext applicationContext) { - PropertyPlaceholderConfigurer propertyPlaceholderConfigurer = new PropertyPlaceholderConfigurer(); - //load database.properties - propertyPlaceholderConfigurer.setLocation(new ClassPathResource("test.properties")); - - applicationContext.addBeanFactoryPostProcessor(propertyPlaceholderConfigurer); - - - } -} \ No newline at end of file diff --git a/resources/src/test/java/com/monkeyk/os/domain/oauth/AccessTokenTest.java b/resources/src/test/java/com/monkeyk/os/domain/oauth/AccessTokenTest.java index 0ab92735402de89bf0611647f9cf8c3178d5cd0e..8929c1d10e454553a94a9f4076d9847dd1006ae4 100644 --- a/resources/src/test/java/com/monkeyk/os/domain/oauth/AccessTokenTest.java +++ b/resources/src/test/java/com/monkeyk/os/domain/oauth/AccessTokenTest.java @@ -1,13 +1,12 @@ package com.monkeyk.os.domain.oauth; import com.monkeyk.os.infrastructure.DateUtils; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; -import java.net.URLDecoder; import java.net.URLEncoder; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.*; + /** * @author Shengzhao Li diff --git a/resources/src/test/java/com/monkeyk/os/infrastructure/jdbc/OAuthRSJdbcRepositoryTest.java b/resources/src/test/java/com/monkeyk/os/infrastructure/jdbc/OAuthRSJdbcRepositoryTest.java index 83c622c4b95684a18885953c276d393f8abab6f8..2e57fa0eb382ae30702dd847c1996889230e3e9b 100644 --- a/resources/src/test/java/com/monkeyk/os/infrastructure/jdbc/OAuthRSJdbcRepositoryTest.java +++ b/resources/src/test/java/com/monkeyk/os/infrastructure/jdbc/OAuthRSJdbcRepositoryTest.java @@ -5,10 +5,11 @@ import com.monkeyk.os.domain.oauth.AccessToken; import com.monkeyk.os.domain.oauth.ClientDetails; import com.monkeyk.os.domain.oauth.OauthCode; import com.monkeyk.os.domain.shared.GuidGenerator; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.testng.annotations.Test; -import static org.testng.Assert.*; +import static org.junit.jupiter.api.Assertions.assertNull; + /** * 15-6-13 diff --git a/resources/src/test/java/com/monkeyk/os/web/ShiroTest.java b/resources/src/test/java/com/monkeyk/os/web/ShiroTest.java index 322f648871e208e290faa6696ef90820ef895981..22fdf7717c5d20d3b75df11f075db8c5d0657389 100644 --- a/resources/src/test/java/com/monkeyk/os/web/ShiroTest.java +++ b/resources/src/test/java/com/monkeyk/os/web/ShiroTest.java @@ -19,13 +19,15 @@ import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.realm.Realm; import org.apache.shiro.realm.SimpleAccountRealm; import org.apache.shiro.subject.Subject; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + import java.util.ArrayList; import java.util.List; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.*; + /** * 15-6-10 @@ -35,7 +37,9 @@ import static org.testng.Assert.assertTrue; public class ShiroTest { - @Test(enabled = false) +// @Test(enabled = false) + @Test() + @Disabled() public void login() { String username = "abc"; //init SecurityManager diff --git a/resources/src/test/java/com/monkeyk/os/web/WebUtilsTest.java b/resources/src/test/java/com/monkeyk/os/web/WebUtilsTest.java index 7e38d21dfe64638cdc20a62100d419fc860c93ae..df37a6302b5000c0157837772159dbb85f89b652 100644 --- a/resources/src/test/java/com/monkeyk/os/web/WebUtilsTest.java +++ b/resources/src/test/java/com/monkeyk/os/web/WebUtilsTest.java @@ -1,11 +1,13 @@ package com.monkeyk.os.web; import org.apache.oltu.oauth2.common.utils.OAuthUtils; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; + import java.util.Arrays; -import static org.testng.Assert.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotNull; + public class WebUtilsTest { diff --git a/resources/src/test/resources/application-test.properties b/resources/src/test/resources/application-test.properties new file mode 100644 index 0000000000000000000000000000000000000000..08b2566cf1afeb2ed133b6f81ce86fef54e797ef --- /dev/null +++ b/resources/src/test/resources/application-test.properties @@ -0,0 +1,18 @@ +# +spring.application.name=Resources +# +server.port=8083 +# +#JDBC configuration information +# connect the same db with authz +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.datasource.url=jdbc:mysql://localhost:3306/oauth2_shiro_test?autoReconnect=true&autoReconnectForPools=true&useUnicode=true&characterEncoding=utf8&useSSL=false +spring.datasource.username=andaily +spring.datasource.password=andaily +#Datasource properties +spring.datasource.type=com.zaxxer.hikari.HikariDataSource +spring.datasource.hikari.maximum-pool-size=20 +# +#Override default +spring.main.allow-bean-definition-overriding=true +# diff --git a/resources/src/test/resources/log4j.properties b/resources/src/test/resources/log4j.properties deleted file mode 100644 index d1deb90399a5af2f332f609f8defe6d54318e3a9..0000000000000000000000000000000000000000 --- a/resources/src/test/resources/log4j.properties +++ /dev/null @@ -1,13 +0,0 @@ -#\u914D\u7F6E\u6839Logger , file, db -log4j.rootLogger=INFO, stdout - -#\u914D\u7F6E\u65E5\u5FD7\u4FE1\u606F\u8F93\u51FA\u76EE\u7684\u5730(appender) -#\u63A7\u5236\u53F0\u8F93\u51FA -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -#\u63A7\u5236\u53F0\u8F93\u51FA\u7684\u9009\u9879(\u53EF\u9009) -#log4j.appender.stdout.Threshold=INFO -#log4j.appender.stdout.ImmediateFlush=true -log4j.appender.stdout.Target=System.out -#\u63A7\u5236\u53F0\u8F93\u51FA\u7684\u683C\u5F0F -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n diff --git a/resources/src/test/resources/test.properties b/resources/src/test/resources/test.properties deleted file mode 100644 index 6044cc49aee9c1a479bd09f10f37446a16a9d782..0000000000000000000000000000000000000000 --- a/resources/src/test/resources/test.properties +++ /dev/null @@ -1,10 +0,0 @@ -#JDBC configuration information -jdbc.driverClassName=com.mysql.jdbc.Driver -############ -# localhost -############ -jdbc.url=jdbc:mysql://localhost:3306/oauth2_shiro_test?autoReconnect=true&autoReconnectForPools=true&useUnicode=true&characterEncoding=utf8 -jdbc.username=andaily -jdbc.password=andaily - -