介绍
提供开箱即用的spring security安全防护及配置。
版本历史
-
0.2.1
使用
使用gradle 插件
添加插件
gradle中
implementation('io.github.baseinsight:plugin-springsecurity-starter:x.x.x')
代码
因为JPA需要的entity、repository类等需要根据项目来具体定制,因此可下载示例基础代码来了解。
描述
是spring security官网的除了Annotation注解、yml集中配置外的第三种标准方式,将授权、鉴权都存储入数据库的模式。spring security参考文档
内置安全entity类
增加entity类5个,增加controller类及相关页面
name | 描述 |
---|---|
BaseRole |
角色 |
BaseUser |
用户 |
BaseUserBaseRole |
用户角色映射 |
Requestmap |
访问控制 |
SystemLoginRecord |
登录日志 |
编写核心安全回调类
因为项目的安全entity类是根据项目变化的,因此需要在工程中创建CoreSecurityComponent类,并实现CoreSecurityOperater接口的所有方法。
// springsecurity的组件实现类,将安全的的查询类库与本地应用的表格结合
@Component
public class CoreSecurityComponent implements CoreSecurityOperater {
@Autowired
RequestmapRepository requestmapRepository;
@Autowired
BaseUserRepository baseUserRepository;
@Autowired
BaseUserBaseRoleRepository baseUserBaseRoleRepository;
@Autowired
BaseRolePrivilegeRepository baseRolePrivilegeRepository;
@Override
public Object findUserByUsername(String username) {
return baseUserRepository.findByUsername(username);
}
@Override
public Object findUserById(Object id) {
return baseUserRepository.findById((String)id);
}
@Override
public List findAllUserAuthorities(Object user) {
List<String> roleNameList= new ArrayList<String>();
List<BaseUserBaseRole> list=baseUserBaseRoleRepository.findAllByBaseUserId(((BaseUser)user).getId());
list.forEach(baseUserBaseRole->{
roleNameList.add(baseUserBaseRole.getBaseRole().getAuthority());
});
return roleNameList;
}
@Override
public List findAllUserPrivileges(List<String> roleNames) {
List<String> privilegeNameList= new ArrayList<String>();
List<BaseRolePrivilege> list=baseRolePrivilegeRepository.findAllByBaseRoleIdIn(roleNames);
list.forEach(baseRolePrivilege->{
privilegeNameList.add(baseRolePrivilege.getPrivilege().getName());
});
return privilegeNameList;
}
@Override
public List findAllRequestmaps() {
return requestmapRepository.findAll(Sort.by(Sort.Direction.ASC,"id"));
}
@Override
public List findAllRequestmapsByRoleName(String roleName) {
return requestmapRepository.findAllByConfigAttributeLike(roleName);
}
}
开发规约
使用系统封装的SpringSecurityUtils类或SpringSecurityService类获取登录用户信息。 因为用户entity类中的外键懒加载原因,不建议将用户entity实例存储进session中.
初始数据
在系统的ApplicationRunner或CommandLineRunner类的run方法中,默认有幂等的几个初始数据的方法。
createDefaultRoles(); //初始化系统角色 createDefaultUsers();//初始化系统用户 createRequestMap();//初始化系统访问控制列表 initMenu();//初始化系统菜单
登录事件
发生系统登录事件时,会自动调用AuthenticationEvents类的相关方法,从而实现登录日志记录.
示例如下:
@Component
public class AuthenticationEvents {
@EventListener
public void onSuccess(AuthenticationSuccessEvent success) {
// ...
Object source=success.getSource();
if(source instanceof UsernamePasswordAuthenticationToken){
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken=(UsernamePasswordAuthenticationToken)source;
User user=(User) usernamePasswordAuthenticationToken.getPrincipal();
user.getUsername();
WebAuthenticationDetails webAuthenticationDetails= (WebAuthenticationDetails)usernamePasswordAuthenticationToken.getDetails();
if(webAuthenticationDetails!=null){
webAuthenticationDetails.getRemoteAddress();
webAuthenticationDetails.getSessionId();
}
}
if(source instanceof AccessToken){
AccessToken accessToken=(AccessToken)source;
User user=(User) accessToken.getPrincipal();
user.getUsername();
}
System.out.println(success.toString());
}
@EventListener
public void onFailure(AbstractAuthenticationFailureEvent failures) {
Object source=failures.getSource();
if(source instanceof UsernamePasswordAuthenticationToken){
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken=(UsernamePasswordAuthenticationToken) source;
}
if(source instanceof AccessToken){
AccessToken accessToken=(AccessToken)source;
}
Exception exception=failures.getException();
System.out.println(failures.toString());
}
}
获取当前登录用户
使用注入的springSecurityService获取当前登录用户:
BaseUser currentUser=baseUserRepository.findById(gbSpringSecurityService.principal.id);
当前用户鉴权操作
使用SpringSecurityUtils类进行用户权限鉴别.
println SpringSecurityUtils.getPrincipalAuthorities(); println SpringSecurityUtils.ifAnyGranted("ROLE_USER,ROLE_ADMIN"); println SpringSecurityUtils.ifAllGranted("ROLE_USER,ROLE_ADMIN"); println SpringSecurityUtils.ifNotGranted("ROLE_USER,ROLE_ADMIN");
controller中
使用注入的sessionRegistry获取当前登录系统的用户数目。
println sessionRegistry.allPrincipals*.username;
同时在线用户数目,有application.yml中的sessionAuthenticationStrategy部分的配置决定.
base: springsecurity: sessionAuthenticationStrategy: maximumSessions: 1 #//-1 为不限,1为只可登录一个用户实例 不可为0 maxSessionsPreventsLogin: false #// true 为后登陆用户异常,false 为先登陆用户session过期 expiredUrl: /login/concurrentSession #为先登陆用户session过期,引导至此路径
提供辅助类
提供辅助类:
SpringSecurityUtils类
静态方法
ifAllGranted(String roles) 当前用户是否全部授予角色
ifNotGranted(String roles) 当前用户是否全部未授予角色
ifAnyGranted(String roles) 当前用户是否授予其中任一角色
isAjax(HttpServletRequest request) 当前是否ajax请求
reauthenticate(String username, String password) 重新认证
PasswordEncoder findPasswordEncoder(String algorithm) //获取指定算法的PasswordEncoder
SpringSecurityService类
需要使用@Autowired 注入
getPrincipal() 获取当前登录principal ,匿名用户为字符串 anonymous
注意:登录用户为 CoreUser 的实例
getCurrentUser() 获取当前用户实例 (BaseUser)
encodePassword(String password)
encodePassword(String password, Object salt = null)
isLoggedIn()
clearCachedRequestmaps() 清除当前缓存的访问控制列表
PasswordEncoder findPasswordEncoder(String algorithm) //获取指定算法的PasswordEncoder
启用cors的处理
默认系统已启用cors
修改application.yml中的 cors值为 enable或disable
base:
springsecurity:
cors: disable
国密算法支持
增加国密算法SM3,SM4的支持
修改application.yml文件
base: 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类添加的,后面是加密后的字符
配置去掉加密后的算法标识
spring security5后,加密的字符串前面会自动添加算法标识{math},如{bcrypt}$2a$10$e8zurQgiO8s5O6rYwMUF..XapBU1WqWi8fmZ895z4lnW8QliEDWYW
可以在application.yml中添加如下配置,去除算法标识,以便与遗留系统集成
base: springsecurity: password: withoutIdPrefix: true
携带算法标识是一个很好的习惯,不推荐将其摘除。可以采用中间视图的形式绕开标识问题与遗留系统集成。 |
修改系统的密码加密
系统中的用户密码加密在BaseUser这个entity类中
@jakarta.persistence.PrePersist public void prePersist() { encodePassword(); } @jakarta.persistence.PreUpdate public void preUpdate() { if(!password.equals(passwordTransient)){ encodePassword(); } } protected void encodePassword() { password = ((PasswordEncoder)SpringUtils.getBean("passwordEncoder")).encode(password); }
JWT方案
可修改配置
默认系统已进行的基本安全配置,若希望修改,可参照如下在yml文件中逐一变更
#spring security
security.basic.enabled: false
base:
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
使用redis存储requestmap
添加项目的radis插件
implementation('io.github.baseinsight:plugin-redis-starter:x.x.x')
增加yml文件配置,启用此功能
base.springsecurity.requestmapGatherToRedis: true;
每次用户访问,系统会自动从redis server下载requestmap的服务器配置
默认增加的redis项
-
键值:base:spring:security:compiledJson
-
键值:base:spring:security:compiledJsonMd5