1. 介绍
提供开箱即用的spring security安全防护及配置,initilizer生成的工程包含基本的使用及示例。
2. 使用
gradle中
implementation('org.yunchen.gb:gb-plugin-springsecurity:1.4.0.0.M1')
3. 描述
是spring security官网的除了Annotation注解、yml集中配置外的第三种标准方式,将授权、鉴权都存储入数据库的模式。spring security参考文档
基本配置
#spring security
security.basic.enabled: false
gb:
springsecurity:
csrf: disable
cors: disable
frameOptions: disabled #disabled,deny,sameOrigin
csrf: disable
cors: enable
corsConfig:
allowCredentials: true # true or false
allowedOrigins: '*' # * or http://localhost:8080
allowedHeaders: '*' #
allowedMethods: '*' # GET,POST or *
corsPath: /**
headers:
- {Access-Control-Expose-Headers: WWW-Authenticate,Authorization,Set-Cookie,X-Frame-Options}
- {Access-Control-Max-Age: 3600}
ajaxHeader: X-Requested-With
password:
encodeHashAsBase64: false
algorithm: bcrypt # bcrypt,pbkdf2,SHA-512,SHA-384,SHA-256,SHA-224,SHA-1,MD5,MD2
securityConfigType : Requestmap
userLookup:
userDomainClassName: org.yunchen.gb.example.demo.domain.core.BaseUser
authorityJoinClassName: org.yunchen.gb.example.demo.domain.core.BaseUserBaseRole
authority.className: org.yunchen.gb.example.demo.domain.core.BaseRole
requestMap.className: org.yunchen.gb.example.demo.domain.core.Requestmap
apf: #/** authenticationProcessingFilter */
filterProcessesUrl: /login/authenticate
auth:
loginFormUrl: /login/auth
alreadyLogin: /login/alreadyLogin #注释此行,则不再做当前session是否登录检查
useForward: false
adh: #/*accessDeniedHandler*/
errorPage: /login/denied
ajaxErrorPage: /login/ajaxDenied
useForward: true
failureHandler:
defaultFailureUrl: /login/authfail
defaultAjaxFailureUrl: /login/authajaxfail
successHandler:
defaultTargetUrl: /workspace/index #登录成功后,若没有rediretUrl则引导进此url
ajaxSuccessUrl: /login/ajaxSuccess
#如注释systemloginRecord 则不进行登录日志记录
systemloginRecord: org.yunchen.gb.example.demo.domain.core.SystemLoginRecord
logout:
afterLogoutUrl: /
filterProcessesUrl: /logoff
sessionAuthenticationStrategy:
maximumSessions: 1 #//-1 为不限,1为只可登录一个用户实例 不可为0
maxSessionsPreventsLogin: false #// true 为后登陆用户异常,false 为先登陆用户被踢出
expiredUrl: /login/concurrentSession
4. 内置安全domain类
增加domain类5个,增加controller类及相关页面
name | 描述 |
---|---|
BaseRole |
角色 |
BaseUser |
用户 |
BaseUserBaseRole |
用户角色映射 |
Requestmap |
访问控制 |
SystemLoginRecord |
登录日志 |
5. 开发规约
使用系统封装的GbSpringSecurityUtils类或GbSpringSecurityService类获取登录用户信息。 因为用户domain中的外键懒加载原因,不建议将domain实例存储进session中.
5.1. 初始数据
在系统的startup类的init方法中,默认有幂等的几个初始数据的方法。
createDefaultRoles(); //初始化系统角色 createDefaultUsers();//初始化系统用户 createRequestMap();//初始化系统访问控制列表 initMenu();//初始化系统菜单
5.2. 登录事件
发生系统登录事件时,会自动调用在startup类的onAuthenticationSuccess方法或onAuthenticationFailure方法,从而实现登录日志记录.
示例如下:
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication){
//保存入登录日志
Map map=[:];
String username=authentication.getPrincipal().username;
map.remoteaddr=request.getRemoteAddr();
map.sessionId=request.getSession().getId();
map.loginTime=new Date();
Timer timer=new Timer();
//100毫秒后分离线程执行
timer.runAfter(100){
SystemLoginRecord.withNewSession{
SystemLoginRecord systemLoginRecord=new SystemLoginRecord(map);
systemLoginRecord.baseUser=BaseUser.findByUsername(username);
systemLoginRecord.save(flush:true);
}
}
}
5.3. 获取当前登录用户
使用注入的gbSpringSecurityService获取当前登录用户:
BaseUser currentUser=BaseUser.read(gbSpringSecurityService.principal.id);
5.4. 当前用户鉴权操作
使用GbSpringSecurityUtils类进行用户权限鉴别.
println GbSpringSecurityUtils.getPrincipalAuthorities(); println GbSpringSecurityUtils.ifAnyGranted("ROLE_USER,ROLE_ADMIN"); println GbSpringSecurityUtils.ifAllGranted("ROLE_USER,ROLE_ADMIN"); println GbSpringSecurityUtils.ifNotGranted("ROLE_USER,ROLE_ADMIN");
5.4.1. controller中
使用注入的sessionRegistry获取当前登录系统的用户数目。
println sessionRegistry.allPrincipals*.username;
详细的演示在WorkspaceController.groovy和LogoutController中。
同时在线用户数目,有application.yml中的sessionAuthenticationStrategy部分的配置决定.
gb: springsecurity: sessionAuthenticationStrategy: maximumSessions: 1 #//-1 为不限,1为只可登录一个用户实例 不可为0 maxSessionsPreventsLogin: false #// true 为后登陆用户异常,false 为先登陆用户session过期 expiredUrl: /login/concurrentSession #为先登陆用户session过期,引导至此路径
5.4.2. 页面中
参看themyleaf3页面的示例
6. 提供辅助类
提供辅助类:
GbSpringSecurityUtils类
静态方法
ifAllGranted(String roles) 当前用户是否全部授予角色
ifNotGranted(String roles) 当前用户是否全部未授予角色
ifAnyGranted(String roles) 当前用户是否授予其中任一角色
isAjax(HttpServletRequest request) 当前是否ajax请求
reauthenticate(String username, String password) 重新认证
PasswordEncoder findPasswordEncoder(String algorithm) //获取指定算法的PasswordEncoder
GbSpringSecurityService类
需要使用@Autowired 注入
getPrincipal() 获取当前登录principal ,匿名用户为字符串 anonymous
注意:登录用户为 org.yunchen.gb.plugin.springsecurity.userdetails.CoreUser 的实例
getCurrentUser() 获取当前用户实例 (BaseUser)
encodePassword(String password)
encodePassword(String password, Object salt = null)
isLoggedIn()
clearCachedRequestmaps() 清除当前缓存的访问控制列表
PasswordEncoder findPasswordEncoder(String algorithm) //获取指定算法的PasswordEncoder
thmeleaf taglib
1. 页面<xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
2. 获取用户信息<span sec:authentication="principal.username" />
3. 角色鉴别 <div sec:authorize="hasAnyRole('ROLE_ADMIN')">
7. 启用cors的处理
若其他域名的应用系统使用本系统的rest接口,出现跨域无法访问的403 错误时,按如下操作:
修改application.yml中的 cors值为 enable
gb:
springsecurity:
active: true
frameOptions: sameOrigin #disabled,deny,sameOrigin
csrf: disable
cors: enable
corsConfig:
allowCredentials: true # true or false
allowedOrigins: '*' # * or http://localhost:8080,http://somesite.com.cn
allowedHeaders: '*' #
allowedMethods: '*' # GET,POST or *
corsPath: /**
8. jsr250 与 requestmap混合工作
8.1. 配置SecurityConfig类
在config目录创建ProjectSecurityConfig类
import org.springframework.boot.autoconfigure.EnableAutoConfiguration import org.springframework.context.annotation.Configuration import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter @Configuration @EnableAutoConfiguration //@EnableGlobalMethodSecurity(securedEnabled=true) @EnableGlobalMethodSecurity(jsr250Enabled=true) class ProjectSecurityConfig extends WebSecurityConfigurerAdapter{ }
也可启用securedEnabled ,就可在项目中使用@Secured注解 |
jsr250Enabled 可以使用@PermitAll,@RolesAllowed,@DenyAll 三个注解 |
8.2. 在controller类中使用
//@Secured("ROLE_ADMIN") //@PermitAll //@RolesAllowed("ROLE_USER,ROLE_ADMIN") @DenyAll public Map index2(){ return [result:true] }
目前方法上的@PermitAll注解无法工作, 需要修改在requestMap表中最后一行, /** 配置为 permitAll |
9. 国密算法支持
增加国密算法SM3,SM4的支持
9.1. 修改application.yml文件
gb: springsecurity: password: encodeHashAsBase64: false algorithm: SM3 # bcrypt,pbkdf2,SHA-512,SHA-384,SHA-256,SHA-224,SHA-1,MD5,MD2,SM3,SM4 sm4Key: 86C63180C2806ED1F47B859EE501215C
sm4Key也可不设置,则会默认使用内置的32位16进制密钥。 |
加密后的效果
admin:{SM3}dc1fd00e3eeeb940ff46f457bf97d66ba7fcc36e0b20802383de142860e76ae6 user:{SM3}92e7fbdcca8b9f36be0638e48e77cbeeb49ef15886b6cd12d46e09d74a232a81
TIP:其中的{idForEncode} 是springsecurity的DelegatingPasswordEncoder类添加的,后面是加密后的字符
10. 配置自定义算法
在项目中存在使用用户id作为盐值的情况,要支持此种情况需要如下配置
10.1. 配置applicaiton.yml
gb: springsecurity: password: encodeHashAsBase64: false algorithm: custom # bcrypt,pbkdf2,SHA-512,SHA-384,SHA-256,SHA-224,SHA-1,MD5,MD2,SM3,SM4,custom (1) useCustomMethodAlgorithm: bcrypt (2)
1 | 配置此项为custom ,在系统中使用CustomPasswordEncoder |
2 | 配置一个辅助的算法encoder,可为上一项除去custom外的任意值 |
10.2. 添加encoder回调方法
在任何标有@GbBootstrap注解的类中,如Startup类,添加如下方法
public String customPasswordEncoder(CoreUser currentLoadUser, PasswordEncoder passwordEncoder,CharSequence plainTextPassword){ //passwordEncoder 是useCustomMethodAlgorithm配置项制定的算法加密器 String encodeStr=passwordEncoder.encode(plainTextPassword) return encodeStr; //因spring security5后不再使用salt,可以自己定制盐值加密类,以便与遗留系统集成 //return Md5Encoder.encode(plainTextPassword,currentLoadUser.id); }
10.3. 添加matches回调方法
在任何标有@GbBootstrap注解的类中,如Startup类,添加如下方法
public boolean customPasswordMatches(CoreUser currentLoadUser, PasswordEncoder passwordEncoder,CharSequence rawPassword, String encodedPassword){ //passwordEncoder 是useCustomMethodAlgorithm配置项制定的算法加密器 return passwordEncoder.matches(rawPassword,encodedPassword) //因spring security5后不再使用salt,可以自己定制盐值加密类,以便与遗留系统集成 //return Md5Encoder.encode(rawPassword,currentLoadUser.id).equals(encodedPassword); }
10.4. 配置去掉加密后的算法标识
spring security5后,加密的字符串前面会自动添加算法标识{math},如{bcrypt}$2a$10$e8zurQgiO8s5O6rYwMUF..XapBU1WqWi8fmZ895z4lnW8QliEDWYW
可以在application.yml中添加如下配置,去除算法标识,以便与遗留系统集成
gb: springsecurity: password: withoutIdPrefix: true
携带算法标识是一个很好的习惯,不推荐将其摘除。可以采用中间视图的形式绕开标识问题与遗留系统集成。 |
10.5. 修改系统的密码加密
系统中的用户密码加密在BaseUser 类中
class BaseUser implements Serializable { 。。。。。。 protected void encodePassword() { CoreUser coreUser=new CoreUser(username, password, enabled, !accountExpired, !passwordExpired, !accountLocked, [], id) password = GbSpringUtils.getBean("passwordEncoder").encode(coreUser,password) } }
11. 配置验证时自定义加载用户
在实际项目中有支持自定义字段匹配用户名的需求,如手机号,邮箱等。
11.1. 配置applicaiton.yml
gb: springsecurity: password: useCustomLoadUser: true
11.2. 添加loaduser回调方法
在任何标有@GbBootstrap注解的类中,如Startup类,添加如下方法
public BaseUser customLoadUser(String inputUsername){ //示例,可自由定制,返回BaseUser实例对象即可 return BaseUser.findByUsernameOrEmailOrPhone(inputUsername,inputUsername,inputUsername); }
11.3. 安全事件
安全事件AppSecurityAuthSuccessEvent、AppSecurityAuthFailureEvent和安全事件基类AppSecurityEvent; 获取AppSecurityAuthSuccessEvent事件的source是一个Map,内容是:[request:request,response:response,authentication:authentication] 获取AppSecurityAuthFailureEvent事件的source是一个Map,内容是:[request:request,response:response,authenticationException:authenticationException]
若订阅安全基类AppSecurityEvent事件,则能收到全部框架发布的安全事件。 authenticationException 是AccountExpiredException、CredentialsExpiredException、 DisabledException、 LockedException、 SessionAuthenticationException、 CaptchaVerificationFailedException六类异常中的一个。 |
订阅示例:
@Configuration
@Slf4j
class NewSecurityAuthSuccessAppListener implements ApplicationListener<AppSecurityAuthSuccessEvent> {
@Override
void onApplicationEvent(AppSecurityAuthSuccessEvent event) {
println "login user is : ${event.source.authentication.principal.username}";
}
}
12. 横向扩展
提供session复制,jwt存储认证的横向扩展能力;支持cookie存储认证信息的横向扩展能力。
12.1. session复制
使用redis进行session复制
12.2. JWT方案
12.3. cookie存储
考虑到使用传统mvc方案的用户,在升级到jwt时,需要前端使用MVVM框架或VUE,成本较高。 因此针对传统mvc模式,提供此cookie存储方案,快速解决横向扩展,减少代码复杂度和迁移成本。
需要客户端浏览器开启cookie支持 |
12.3.1. 使用步骤
在application.yml中增加配置
gb: springsecurity: scale: enableCookie: true #使用cookie存储认证信息 httpOnly: true #不允许客户端js读取 secure: false #只支持https协议 tokenValiditySeconds: 1209600 # 14 days domainName: #cookie域名,为空或不设置则使用访问路径localhost或IP
经过以上两步后,系统用户登录后,会自动将认证信息写入浏览器cookie; 无论是服务端重启或多服务轮询,都会优先检验cookie,再检验sessionId.
用户在系统中,主动登出/logout/index 或访问/logoff 时 或关闭浏览器时,系统都会自动清除cookie.
12.4. 使用redis存储requestmap
12.4.1. 添加项目的data-radis插件
implementation('org.yunchen.gb:gb-plugin-data-redis:1.4.0.0.M1')
12.4.2. 增加yml文件配置,启用此功能
gb.springsecurity.requestmap.gatherToRedis: true;
12.4.3. 每次用户访问,系统会自动从redis server下载requestmap的服务器配置
12.4.4. 默认增加的redis项
-
键值:gb:spring:security:compiledJson
-
键值:gb:spring:security:compiledJsonMd5