lib-security为了方便应用对资源的权限控制而生,基于spring-security,做了很多功能增强和自动化配置,来简化应用对spring-security的集成成本,使开发者很快的将权限控制的功能集成到自己的应用中。
curl -s -k -L -C - -b "oraclelicense=accept-securebackup-cookie" http://download.oracle.com/otn-pub/java/jce/8/jce_policy-8.zip > /tmp/policy.zip sudo sh -c "unzip -p /tmp/policy.zip UnlimitedJCEPolicyJDK8/local_policy.jar > ${JAVA_HOME}/jre/lib/security/local_policy.jar" sudo sh -c "unzip -p /tmp/policy.zip UnlimitedJCEPolicyJDK8/US_export_policy.jar > ${JAVA_HOME}/jre/lib/security/US_export_policy.jar"
项目需要先引入oss-release,然后使用如下方式引入lib-security。
cn.home1 oss-lib-security-spring-boot-${spring-boot.version}
使用lib-security,需要指定spring-boot.version,目前支持的springboot版本如下,
为方便测试,可以加入spring-security-test的依赖。
<dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency>
Support 4 app types.
MIXED: RESTful API and template pages, with all authentication method.
RESOURCE: RESTful resource application with token and oauth authentication method only.
RESTFUL: RESTful application, no template pages, with all authentication method.
TEMPLATE: Template based application, no RESTful API, with all authentication method.
Integrated with oss-lib-errorhandle.
Standard error info.
AES based encrypted cookie. For this, yml definition ex: > app.security.cookieKey: ‘AES256_CBC16:YDD7uVFNpvkId8HWI6xTfOeRW3O6Wk3FDuGJdnGDhiD=’
An auth mechanism that carrying auth info by application/x-www-form-urlencoded form fields.
> app.security.loginPage : ‘/login.do’ > app.security.loginProcessingUrl: ‘/login’ > app.security.logoutUrl: ‘/logout’
Test with command line tool. TODO
Generate RSA keypair for password encryption. loginkey for Rsa pair key and loginPublicKeyUrl for client get rsaPublicKey.
> app.security.loginKey: ‘RSA1024_PAIR_PKCS1_X509:MIICWwIBAAKBgQCRXunHa+5hw+L39UZoIFhPwPR9gBvdlEvlXAH4biP+GNb8OH77OjMsfjfs3dXs3QqwXMajJUKZFpX1v+w/ilZ9muvty4/wfQlx5qy305Ui6mRqpIQKKxkwgAS+LTVCh+AeFWfB9G/OVA/khp73VQ3/IPoqgcPG+e/RD/kdFwTozwIDAQABAoGAALbYqzQqjaxqtxErcWOHS40FJoC0olgSL2ROViwkXSE+HSsh5JYankNYfv9wB6PmY4txJmgOdbYYsoZo4L8LQbzV02cEzAibAltvS8JUZ0XWNbnhRnSvY+GJ24hf1BHIJGjBnkQDK/XrDw8Xzt2bSDlWyWH4iEC7PgpghQpHRZECQQDB1GK0U3iDofSJkdLpypxWE6T6MBl7RJCOROj56cXIaVcamOKD1D+x+aDrP7wH2Crhn5X83cTDGekDEIzs0tHjAkEAv/+A4QP61ZgEKn+1gk7RHw/tWHBLWhb1EcIpShBNWx8sB4G8wI7brf858iLXl9NhROkcqYmbchG3D6SnYZmRJQJAR8kxBTgk2huRRaIMSyoO3JJJ95740P6Dyy0aW/SIm8Dn0aHtwoVJUdDyGC5ypTUaLJW+Jvi7dsaR1eC7ULqDoQJAHoSNRrbsOuEz4FF2V2URxl8wubr3rzUw9Qaoq3YV5aL5y6OqjeznLCwCWOOC40YdEuf+v0/5HlOEhn8Ef/X55QJAKktna3oDVsfsTH6REVLz8kjvhILJ5yk493/yjTZxpcpcVv8LGjuCKtIn3NdCRoeIC1K+oLzbBB6U+J8Uu5aeSA==:MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCRXunHa+5hw+L39UZoIFhPwPR9gBvdlEvlXAH4biP+GNb8OH77OjMsfjfs3dXs3QqwXMajJUKZFpX1v+w/ilZ9muvty4/wfQlx5qy305Ui6mRqpIQKKxkwgAS+LTVCh+AeFWfB9G/OVA/khp73VQ3/IPoqgcPG+e/RD/kdFwTozwIDAQAB’ > app.security.loginPublicKeyUrl: ‘/login/rsaPublicKey’
Test with command line tool. TODO
Application needs to provide a cn.home1.oss.lib.security.api.VerifyCodeProvider implementation.
app.security.verifyCode: true
Test with command line tool. TODO
Application needs to implements cn.home1.oss.lib.security.api.User and provide extended properties by public Map<String, Object> getProperties() method.
Role represents a group of users has the same privileges.
User ..M:N .. Role
User should provide org.springframework.security.core.GrantedAuthority implementations.
(by extends cn.home1.oss.lib.security.api.AbstractRole) Authority name should start with “ROLE_” (*cn.home1.oss.lib.security.api.Security.ROLE_PREFIX*).
Privilege represents an action to a resource.
Role ..M:N .. Privilege
User should provide cn.home1.oss.lib.security.api.StaticPrivilege implementations.
Privilege name should start with “PRIVILEGE_” (*cn.home1.oss.lib.security.api.StaticPrivilege.PRIVILEGE_PREFIX*).
An auth per request mechanism that carrying base64 auth info by request header.
Should be used with https.
Test with command line tool. TODO curl -i -X GET -L http://test_user:user_pass@127.0.0.1:8080/users/current
An auth per request mechanism that carrying password hashed auth info by request header.
Should be used with https.
Test with command line tool. TODO
Get user info. TODO
Request RSA public key for password encryption. TODO
Form auth. TODO
Basic auth. TODO
Digest auth. TODO
app: type:TEMPLATE # 类型,可选的值为 MIXED|TEMPLATE|RESTFUL|RESOURCE security: basePath: '/auth' # auth相关的url前缀,下面相关的url默认都会拼接此前缀,默认是 /auth jwtKey: 'keySpec:value' # 对jwt进行签名计算用的key permited: '/permited/**,/img/*' # 无须认证授权的url,多个用,隔开 app.security.enabled=true # true | false, TODO 涉及哪些功能的开关 app.security.cookieKey='keySpec:value' # AES key for encrypt / decrypt cookie, TODO 如何获得, 格式 # auto generate auth token of defaultTestUser app.security.defaultTestUser= # default test user's name, see: cn.home1.oss.lib.security.api.BaseUserDetailsAuthenticationProvider .
app.security.authEntryPoint=restful # restful | 403 | 401 | template (loginPage), TODO 何时用handler, 何时用entryPoint app.security.authFailureHandler=restful # restful | template (loginPage), handler on auth failed. app.security.authSucessHandler=restful # restful | template, handler on auth succeed. TODO test this app.security.loginKey='keySpec:value' # RSA key pair for encrypt password form field. TODO 如何获得, 格式 app.security.loginPublicKeyUrl=/api/login/publicKey # URL for RSA public key, TODO 如何获得, 格式 app.security.loginPage=/login # custom login page TODO test this in different app types app.security.loginProcessingUrl=/api/login # URL for login form POST request. app.security.logoutUrl=/api/logout # URL for logout request. app.security.verifyCode=false # true | false, enable verifyCode or not. User needs to provide cn.home1.oss.lib.security.api.VerifyCodeProvider if enabled.
Filter | Description | Ex |
---|---|---|
PermitedRequestConfiguration | for some request without auth | /img,/js |
CsrfConfiguration | generate XSRF-TOKEN | |
FormAuthConfiguration | for | |
BasicAuthConfiguration | security basic auth | |
PreAuthConfiguration | fot cookie or token |
TODO
org.springframework.security.web.FilterChainProxy#doFilterInternal ... private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { ... if (filters == null || filters.size() == 0) { ... see: this.filterChains org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource ... public Collection<ConfigAttribute> getAttributes(Object object) { ... for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : requestMap .entrySet()) { ... see: this.requestMap
RestFul方式下,我们约定大部分的接口都是RestFul接口,但是不限制全部接口都必须是Restful接口,允许Template方式的接口存在。
app: security:RestFul enabled: true # 是否启用 basePath: '/auth' # auth相关的url前缀,下面相关的url默认都会拼接此前缀,默认是 /auth loginProcessingUrl: '/login' # form表单提交的用户登录url,默认是 /login logoutUrl: '/logout' # 用户登出的url,默认是 /logout loginPublicKeyUrl: /login/publicKey # 获取RSA公钥的url,默认是 /login/publicKey defaultTestUser: test_user # 默认测试用户 cookieKey: 'keySpec:value' # 对cookie进行aes加密计算用的key jwtKey: 'keySpec:value' # 对jwt进行签名计算用的key
用户模型
继承cn.home1.oss.lib.security.api.AbstractUser类并实现父类抽象方法,按需覆写父类方法。关于额外的用户信息,可以通过扩展public Map<String, Object> getProperties()来实现
service实现
可以重写该类的 testUsers 方法,生成测试用户方便测试。 lib-security配置中的defaultTestUser属性需要配合该方法使用,测试用户必须在用户提供的测试用户列表中进行选择。系统启动时,会将测试用户插入到持久化存储或内存中,具体依赖于用户实现的UserDetailsService中的save(final User user) 方法,自定义tsetUser方法如下:
@Override protected List<User> testUsers() { return ImmutableList.of( User.userBuilder().email("user@somedomain.com").enabled(true) // .name("test_user").password("user_pass") // .roles(ImmutableSet.of(UserRole.ROLE_USER_AUTHORITY)) // .build(), User.userBuilder().email("admin@somedomain.com").enabled(true) // .name("test_admin").password("admin_pass") // .roles(ImmutableSet.of(UserRole.ROLE_ADMIN_AUTHORITY)) // .build()); }
curl -i -X POST -L -c COOKIE –data “username=test_user&password=user_pass” http://127.0.0.1:8080:8080/auth/login
Response:
HTTP/1.1 200 OK Date: Mon, 12 Dec 2016 07:01:36 GMT Set-Cookie: generic_user=QPBhNLLca0YvYt0l29hFCP1foCi8ca2FPy7uk0On80TlEY5%2BqUasIzEp6rjc7crxahUJIdbGig9f66iyf2WbpVGTxFwY44hA04Gz6Z4M77P9fhNYyYx7XrUh8BC1J8l%2Fx0FIcxaQ5ASwNB%2FVSu00xJh8%2BizCYvdXBumKckzrclXrEvDlvhkAZzo4jWgm%2BsrbpUDA13S1GzfspG9fOzMBaZ7ais2NsHq%2FWL49NOzMcHXxJKruHeK7jIUk%2B1KNv5RpDZ%2BKtBAWysWKjvgTG%2B3xgbGRjWArCQZwi2YK2AY1Pj6MDeRfdEsdUwY%2F%2B3GejqWAdhYIvQMKJFEZ4KDzJXeX5wqYuYC%2FkwxnVG1hCrIQr7IUoOkXGUMRXoAvtMRKVTLD2creOLCbvDM3ig2%2F3VxYBxyCG0nrwHpkzmDe33E0JpQb1v0hxvYhnC7yRAQwUIYiiVoGRKaK9dAuYtAJIDKDM33R3%2FbZkpN%2Bt7on5%2FEDFwh3qkWVif3F0Iy%2FquyAsQKm2QZ7v5Iiod9EVwEcK3hy%2FoXr6Z874Zg%2B8vtr9EZ4QvYucxHren1apojB0NFCxIuUBvIEX0dZd4qNQsITFWi4JR51FBMfy7LXOY%2BqspfSZzLQKWkg%2BBQEtVaaXh1hcDfcc8YKau5AHN69Z8pKjm%2Bl%2BWi%2Fx2q5dz2sYoJ9%2FZLn1HymgjefyyoQspvKaK3vOxIWd%2FvJa5EsfknAoIMKDrTN3A1TuNK%2B5EZz8UprSikJt1N6xV0FY3aoOQjyKhxkaQ%2BZmNj27bjc6X1f%2BbuIBmV85%2BuN3ZhINcDV9hSfqdJqu06aOuUGq8icuL14h6leyugrBsPF2kbiLppy%2Fj27Srdag5xfx6J%2BJtaKwVc1IWuTy63fWMhc%2Bk%2Fzuks6wd594OEU;Version=1;Path=/;Expires=Tue, 06-Dec-2016 13:26:35 GMT;Max-Age=3600;HttpOnly;Comment=generic_user Expires: Thu, 01 Jan 1970 00:00:00 GMT X-Auth-Token: r%2BrlCMT9lZSHW%2BoOp2Lp2OXvR%2Bq%2FrOVGIBe39RxZIkZgz7lC8WrjXhI%2B40nN89W2aB0JZO4X7sT0%2BWxo6Aglv%2FqyvwjUYB8Q2SCLqqx%2BmpaoOXBXLbwQQqdG7EBGc8MPl%2BkWR4g69pZEFurtMUEOSYu9dEXpKN2yVyFGo9EcX1kE9YfKvNTBrSyS1S2Vh0l1SFtwKVBFUJ5XQSrX%2FoSn0syaIAw8dPPetDHns9s0AfYyqStbcH5uOlr3QVTsN%2Fx4jS0%2F9%2FlK9tIvBFQCdhqcNsmgZXTqM36Zxw2Y4sV6Ri6u22c2Hk6Y4jPWVcs%2F3KfLzQmD4b1zK2U2yxYQ6gqEeDBdDzyke9ozqRJuelT%2BK7G28zbANXRr91q%2FgGlG3AkUYo7Ak%2FBhq88O8z799iDfmHxca1Jfb8jKy1qGJn77jwIKxYpZzEpZNm1T%2BkWpMe0lRvqZ2eHg7VYFKWiHgPQleNwdosxbmOrbZqtEejNVutrgVN%2Fm1JytPnTldZzueyexIw2MrFDZvZSrRuzt0CMWoCA9nlriTaUksILZjFuJeeGjGHhzQXI1X3xPhet6qk8pkrj4BNiHKZyiAcOw6mtyeLiKuyVoWWMbBN3mJsU7LWpj3Kg18%2Bu%2BcF%2F%2BbPH%2BDiz%2BPrnhyYEeHOeavN8uOkvQU%2Fws12VSkY8zC9SlS3RIfZSh2RYoVFXScQgDXjPYxFCJjULEV5ARfqmqEyOXNm%2Fp2YnEG3H%2F4EQ8nJMTDJlD7UjK%2FVxRSgTzlFyNSVTkRuDV9mSXyVlpdxStMgCbCcVYOKDKA5MI%2Fhtk7kB%2BsBjXjXE2cPGt3%2B9A2%2FKOj%2BL8pkFR%2BO8vDxTFuP4f4Wy%2BZvb6G4iHnwBF1kSnnEHVCPDjsvJKvvDYg7eGGAWCcNMDi9JL Content-Type: application/json;charset=UTF-8 X-Content-Type-Options: nosniff X-XSS-Protection: 1; mode=block X-Frame-Options: SAMEORIGIN Transfer-Encoding: chunked
{“authorities”:[“ROLE_USER”,“PRIVILEGE_DATA_VIEW”],“enabled”:true,“id”:“1”,“name”:“test_user”,“password”:“[PROTECTED]”,“username”:“test_user”}
lib-security分别在 Cookie 和 X-Auth-Token header中,返回了token信息,这里由于没有配置加密,所以token仅仅做了URLEncode。 在Response Body中返回用户基本信息。
支持客户端在请求资源时,以如下两种方式携带token信息(token信息即用户登录成功后,在cookie和X-Auth-Token中返回的数据)。
Cookie方式
curl -i -X GET -L --COOKIE "generic_user=%7B%22accountNonExpired%22%3Atrue%2C%22accountNonLocked%22%3Atrue%2C%22authorities%22%3A%5B%22ROLE_USER%22%2C%22PRIVILEGE_DATA_VIEW%22%5D%2C%22credentialsNonExpired%22%3Atrue%2C%22enabled%22%3Atrue%2C%22password%22%3Anull%2C%22username%22%3A%22UT_USER%3E1%3Etest_user%22%2C%22properties%22%3A%7B%22email%22%3A%22user%40somedomain.com%22%7D%2C%22timestamp%22%3A%222016-12-12T15%3A01%3A36.918%2B08%3A00%22%2C%22uuid%22%3A%22b8dd33cca0a6422aace507aaf6d06206%22%2C%22id%22%3A%221%22%7D;Version=1;Path=/;Expires=Mon, 12-Dec-2016 08:01:37 GMT;Max-Age=3600;HttpOnly;Comment=generic_user" http://127.0.0.1:8080:8080/users/current Response HTTP/1.1 200 OK Date: Mon, 12 Dec 2016 07:13:05 GMT X-Application-Context: application-quick:8080 Content-Type: application/json;charset=UTF-8 X-Content-Type-Options: nosniff X-XSS-Protection: 1; mode=block Cache-Control: no-cache, no-store, max-age=0, must-revalidate Pragma: no-cache Expires: 0 X-Frame-Options: SAMEORIGIN Transfer-Encoding: chunked {"authorities":["ROLE_USER","PRIVILEGE_DATA_VIEW"],"enabled":true,"id":"1","name":"test_user","password":"[PROTECTED]","username":"test_user"}
X-Auth-Token方式
curl -i -X GET -L --HEADER "X-Auth-Token: %7B%22accountNonExpired%22%3Atrue%2C%22accountNonLocked%22%3Atrue%2C%22authorities%22%3A%5B%22ROLE_USER%22%2C%22PRIVILEGE_DATA_VIEW%22%5D%2C%22credentialsNonExpired%22%3Atrue%2C%22enabled%22%3Atrue%2C%22password%22%3Anull%2C%22username%22%3A%22UT_USER%3E1%3Etest_user%22%2C%22properties%22%3A%7B%22email%22%3A%22user%40somedomain.com%22%7D%2C%22timestamp%22%3A%222016-12-12T15%3A01%3A36.918%2B08%3A00%22%2C%22uuid%22%3A%22b8dd33cca0a6422aace507aaf6d06206%22%2C%22id%22%3A%221%22%7D" http://127.0.0.1:8080:8080/users/current Response HTTP/1.1 200 OK Date: Mon, 12 Dec 2016 07:15:31 GMT X-Application-Context: application-quick:8080 Content-Type: application/json;charset=UTF-8 X-Content-Type-Options: nosniff X-XSS-Protection: 1; mode=block Cache-Control: no-cache, no-store, max-age=0, must-revalidate Pragma: no-cache Expires: 0 X-Frame-Options: SAMEORIGIN Transfer-Encoding: chunked {"authorities":["ROLE_USER","PRIVILEGE_DATA_VIEW"],"enabled":true,"id":"1","name":"test_user","password":"[PROTECTED]","username":"test_user"}
curl -i -X GET -L -b COOKIE http://127.0.0.1:8080:8080/auth/logout
Response
HTTP/1.1 200 OK Date: Mon, 12 Dec 2016 07:16:41 GMT Set-Cookie: generic_user=;Version=1;Path=/;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;HttpOnly;Comment=generic_user Expires: Thu, 01 Jan 1970 00:00:00 GMT X-Content-Type-Options: nosniff X-XSS-Protection: 1; mode=block X-Frame-Options: SAMEORIGIN Content-Length: 0
登出请求成功后,用户的cookie和token信息都被清空了,
模版应用包括Velocity、Thymeleaf等模版构建的web应用,前后端的资源集成在一起部署。
app: type: TEMPLATE security: basePath: '/auth' cookieKey: 'AES256_CBC16:YDD7uVFNpvkId8HWI6xTfOeRW3O6Wk3FDuGJdnGDhiD=' defaultTestUser: 'test_user' enabled: true jwtKey: 'HS512:Ve+/vU5u77+977+977+977+977+9Acu/77+977+977+9OXrvv71XH++/vRLvv73vv73vv73vv71577+9fQLvv73vv71eB++/vW7vv71g77+977+977+9L++/vWDvv73vv73vv71577+9VO+/ve+/vR3vv73vv73Coemfv++/ve+/vQ==' loginKey: 'RSA1024_PAIR_PKCS1_X509:MIICWwIBAAKBgQCRXunHa+5hw+L39UZoIFhPwPR9gBvdlEvlXAH4biP+GNb8OH77OjMsfjfs3dXs3QqwXMajJUKZFpX1v+w/ilZ9muvty4/wfQlx5qy305Ui6mRqpIQKKxkwgAS+LTVCh+AeFWfB9G/OVA/khp73VQ3/IPoqgcPG+e/RD/kdFwTozwIDAQABAoGAALbYqzQqjaxqtxErcWOHS40FJoC0olgSL2ROViwkXSE+HSsh5JYankNYfv9wB6PmY4txJmgOdbYYsoZo4L8LQbzV02cEzAibAltvS8JUZ0XWNbnhRnSvY+GJ24hf1BHIJGjBnkQDK/XrDw8Xzt2bSDlWyWH4iEC7PgpghQpHRZECQQDB1GK0U3iDofSJkdLpypxWE6T6MBl7RJCOROj56cXIaVcamOKD1D+x+aDrP7wH2Crhn5X83cTDGekDEIzs0tHjAkEAv/+A4QP61ZgEKn+1gk7RHw/tWHBLWhb1EcIpShBNWx8sB4G8wI7brf858iLXl9NhROkcqYmbchG3D6SnYZmRJQJAR8kxBTgk2huRRaIMSyoO3JJJ95740P6Dyy0aW/SIm8Dn0aHtwoVJUdDyGC5ypTUaLJW+Jvi7dsaR1eC7ULqDoQJAHoSNRrbsOuEz4FF2V2URxl8wubr3rzUw9Qaoq3YV5aL5y6OqjeznLCwCWOOC40YdEuf+v0/5HlOEhn8Ef/X55QJAKktna3oDVsfsTH6REVLz8kjvhILJ5yk493/yjTZxpcpcVv8LGjuCKtIn3NdCRoeIC1K+oLzbBB6U+J8Uu5aeSA==:MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCRXunHa+5hw+L39UZoIFhPwPR9gBvdlEvlXAH4biP+GNb8OH77OjMsfjfs3dXs3QqwXMajJUKZFpX1v+w/ilZ9muvty4/wfQlx5qy305Ui6mRqpIQKKxkwgAS+LTVCh+AeFWfB9G/OVA/khp73VQ3/IPoqgcPG+e/RD/kdFwTozwIDAQAB' loginProcessingUrl: '/login' loginPublicKeyUrl: '/login/rsaPublicKey' logoutUrl: '/logout' permited: '/fonts/*,/img/*,/images/*,/js/*,/login*,/css/*' loginPage : '/login.do' authSucessHandler : '/cluster/list.do'
继承cn.home1.oss.lib.security.api.AbstractUser类并实现父类抽象方法,按需覆写父类方法。关于额外的用户信息,可以通过扩展public Map<String, Object> getProperties()来实现
继承 cn.home1.oss.lib.security.api.AbstractRole 类,来存储用户角色的信息,
继承cn.home1.oss.lib.security.api.StaticPrivilege类,来做静态资源权限的定义,
需要实现进入登陆页的入口和登陆成功后跳转的页面,这里成功后跳转到的页面没有列出
@Controller @RequestMapping("/auth") public class AuthAction extends AbstractAction { /** * 登录页跳转 * * @param map * @param request * @return */ @RequestMapping(value = "/login", method = RequestMethod.GET) public String login(ModelMap map, HttpServletRequest request) { map.put(getFlag(), "active"); map.put(TITLE, "login"); return "login"; } }