@Override
public void onLogoutSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
resp.setContentType(“application/json;charset=utf-8”);
PrintWriter out = resp.getWriter();
out.write(“logout success”);
out.flush();
}
})
.permitAll()
.and()
2.5、方法安全
上面介绍的认证与授权都是基于 URL 的,也可以通过注解来灵活地配置方法安全,要使用相关注解,首先要通过@EnableGloba!MethodSecurity 注解开启基于注解的安全配置:
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled=true,securedEnabled=true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
}
-
prePostEnabled=true会解锁@PreAuthorize和@PostAuthorize两个注解,@PreAuthorize会在执行方法前验证,@PostAuthorize会在执行方法后验证。
-
securedEnabled=true会解锁@Secured 注解。
@Service
public class MethodService {
//示访问该方法需要 ADMIN 角色
@Secured(“ROLE ADMIN”)
public String admin () {
return "hello admin ";
}
//访问该方法既需要ADMIN角色又需要USER角色
@PreAuthorize(“hasRole (‘ADMIN’) and hasRole ('USER ')”)
public String user(){
return “Hello User”;
}
//访问该方法需要ADMIN 或 USER角色
@PreAuthorize(“hasAnyRole(‘ADMIN’,‘USER’)”)
public String any(){
return “Hello Every One”;
}
}
3.1、加密方案
密码加密一般会用到散列函数,又称散列算法、哈希函数,这是一种从任何数据中创建数字“指纹”的方法。散列函数把消息或数据压缩成摘要,使得数据量变小,将数据的格式固定下来,然后将数据打乱混合,重新创建一个散列值。散列值通常用一个短的随机字母和数字组成的字符串来代表。好的散列函数在输入域中很少出现散列冲突。在散列表和数据处理中,不抑制冲突来区别数据会使得数据库记录更难找到。我们常用的散列函数有 MD5 消息摘要算法、安全散列算法(Secure Hash Algorithm )。
123456 —MD5—> e10adc3949ba59abbe56e057f20f883e
实际上,上面的实例在现实使用中还存在着一个不小的问题。虽然 MD5 算法是不可逆的,但是因为它对同一个字符串计算的结果是唯一 的,所以一些人可能会使用“字典攻击”的方式来攻破 MD5 加密的系统。这虽然属于暴力解密,却十分有效,因为大多数系统的用户密码都不会很长。
为了解决这个问题,我们可以使用盐值加密“salt-source”,所谓加盐加密,是指在加密之前,为原文附上额外的随机值,再进行加密。具体实现方法并不固定。
3.2、 实践
Spring Security内置了密码加密机制,只需使用一个PasswordEncoder接口即可。
PasswordEncoder接口定义了encode和matches两个方法,当用数据库存储用户密码时,加密过程用 encode方法,matches方法用于判断用户登录时输入的密码是否正确。
Spring Security 还内置了几种常用的 PasswordEncoder 接口,例如, StandardPasswordEncoder中的常规摘要算法(SHA-256等)、BCryptPasswordEncoder加密,以及类似 BCrypt的慢散列加密Pbkdf2PasswordEncoder等,官方推荐使用BCryptPasswordEncoder。
配置密码加密非常简单:
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
同样可以自定义加密方式,例如不想使用推荐的BCryptPasswordEncoder,想使用其它的加密方法,例如MD5加密,很简单,我们自己实现一个PasswordEncoder,在配置中使用自定义的加密类即可。
public class Md5PasswordEncoder implements PasswordEncoder {
@Override
public String encode(CharSequence charSequence) {
//省略md5加密过程
return md5String;
}
@Override
public boolean matches(CharSequence charSequence, String s) {
//省略比对过程
return false;
}
}
@Bean
PasswordEncoder passwordEncoder() {
return new Md5PasswordEncoder();
}
在真实项目中,用户的基本信息以及角色等都存储在数据库中,因此需要从数据库中获取数据进行认证。
4.1、数据库设计
一共三张表,分别是用户表、角色表、用户_角色关联表。
创建表并插入一些测试数据:
SET FOREIGN_KEY_CHECKS=0;
– Table structure for role
DROP TABLE IF EXISTS role
;
CREATE TABLE role
(
id
int(11) NOT NULL AUTO_INCREMENT,
rolename
varchar(50) NOT NULL COMMENT ‘角色名’,
note
varchar(255) NOT NULL COMMENT ‘角色描述’,
PRIMARY KEY (id
)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT=‘角色表’;
– Records of role
INSERT INTO role
VALUES (‘1’, ‘ROLE_ADMIN’, ‘管理员’);
INSERT INTO role
VALUES (‘2’, ‘ROLE_DBA’, ‘数据库管理员’);
INSERT INTO role
VALUES (‘3’, ‘ROLE_USER’, ‘用户’);
– Table structure for user
DROP TABLE IF EXISTS user
;
CREATE TABLE user
(
id
int(11) NOT NULL AUTO_INCREMENT COMMENT ‘主键’,
username
varchar(50) NOT NULL COMMENT ‘用户名’,
password
varchar(255) NOT NULL COMMENT ‘密码’,
enabled
tinyint(1) NOT NULL COMMENT ‘是否可用,1表示可用,0表示不可用’,
locked
tinyint(1) NOT NULL COMMENT ‘是否上锁,1表示上锁,0表示未上锁’,
PRIMARY KEY (id
)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT=‘关联表’;
– Records of user
INSERT INTO user
VALUES (‘1’, ‘admin’, ‘$2a$10$7zE.mjxSWV6w6jyh8iL8Q.5ouGokPg1PuL531YKPmRWCysN30I.qO’, ‘1’, ‘0’);
INSERT INTO user
VALUES (‘2’, ‘root’, ‘$2a$10$7zE.mjxSWV6w6jyh8iL8Q.5ouGokPg1PuL531YKPmRWCysN30I.qO’, ‘1’, ‘0’);
INSERT INTO user
VALUES (‘3’, ‘laosan’, ‘$2a$10$7zE.mjxSWV6w6jyh8iL8Q.5ouGokPg1PuL531YKPmRWCysN30I.qO’, ‘1’, ‘0’);
– Table structure for user_role
DROP TABLE IF EXISTS user_role
;
CREATE TABLE user_role
(
id
int(11) NOT NULL AUTO_INCREMENT,
uid
int(11) NOT NULL COMMENT ‘用户id’,
rid
int(11) NOT NULL COMMENT ‘角色id’,
PRIMARY KEY (id
)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 COMMENT=‘用户-角色关联表’;
– Records of user_role
INSERT INTO user_role
VALUES (‘1’, ‘1’, ‘1’);
INSERT INTO user_role
VALUES (‘2’, ‘2’, ‘2’);
INSERT INTO user_role
VALUES (‘3’, ‘3’, ‘3’);
INSERT INTO user_role
VALUES (‘4’, ‘1’, ‘3’);
INSERT INTO user_role
VALUES (‘5’, ‘1’, ‘2’);
- 角色名有一个默认的前缀"ROLE_"
4.2、创建项目
这里选择MyBatis作为持久层框架,添加依赖:
org.springframework.boot
spring-boot-starter-security
org.springframework.boot
spring-boot-starter-web
mysql
mysql-connector-java
runtime
com.alibaba
druid
1.1.12
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.3.2
4.3、application.properties
数据库连接和MyBatis相关配置:
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url=jdbc:mysql://localhost:3306/demo_security?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
mybatis.type-aliases-package=edu.hpu.pojo
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
4.4、实体类
用户实体类需要实现UserDetails接口:
public class User implements UserDetails {
private Integer id;
private String username;
private String password;
private Boolean enabled;
private Boolean locked;
private List roles;
/**
-
获取当前用户对象所具有的角色信息
-
@return
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List authorities = new ArrayList<>() ;
for (Role role : roles){
authorities.add (new SimpleGrantedAuthority (role.getRolename()) ) ;
}
return authorities;
}
/**
-
获取当前用户的密码
-
@return
*/
@Override
public String getPassword() {
return password;
}
/**
-
获取d当前用户的用户名
-
@return
*/
@Override
public String getUsername() {
return username;
}
/**
-
当前账户是否未过期
-
@return
*/
@Override
public boolean isAccountNonExpired() {
return true;
}
/**
-
当前账户是否未锁定
-
@return
*/
@Override
public boolean isAccountNonLocked() {
return !locked;
}
/**
-
当前账户密码是否未过期
-
@return
*/
@Override
public boolean isCredentialsNonExpired() {
return true;
}
/**
-
当前账户是否可用
-
@return
*/
@Override
public boolean isEnabled() {
return enabled;
}
//省略getter、setter
}
-
实现了UserDetails接口的七个方法,方法作用见注释
-
用户根据实际情况设直这个方法的返回值 因为默认情况下不需要开发者自己进行密码角色等信息的比对,开发者只需要提供相关信息即可。例如 getPassword()方法返回的密码和用户输入的登录密码不匹配,会自动抛出BadCredentialsException 异常。
-
getAuthorities()方法用来获取当前用户所具有的角色信息。
角色实体类:
public class Role {
private Integer id;
private String rolename;
private String note;
//省略getter、setter
}
4.5、持久层
接口:
@Mapper
public interface UserMapper {
User loadUserByUsername(String username);
List getUserRolesByUid (Integer id) ;
}
对应的映射文件:
<?xml version="1.0" encoding="UTF-8"?>select * from user where username=#{username}
select r.* from role r
join user_role ur on r.id=ur.rid
join user u on u.id=ur.uid
where u.id=#{id}
4.6、服务层
UserService需要实现UserDetailsService接口
@Service
public class UserService implements UserDetailsService {
@Autowired
UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userMapper.loadUserByUsername(username);
if (user==null){
throw new UsernameNotFoundException(“账号不存在”);
}
user.setRoles(userMapper.getUserRolesByUid(user.getId()));
return user;
}
}
-
实现 UserDetailsService 接口,并实现该接口中的 loadUserByUsername 方法,该方法的参数就是用户登录时输入的用户名,通过用户名去数据库中查找用户,如果没有查找到用户,就抛出 个账户不存在的异常,如果查找到了用户,就继续查找该用户所具有的角色信息,并将获取到的 user 对象返回,再由系统提供的 DaoAuthenticationProvider 类去比对密码是否正确。
-
loadUserByUsername 方法将在用户登录时自动调用。
4.7、配置
这里是一个比较精简的配置,用户名和密码从数据库中获取:
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserService userService;
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService (userService);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers(“/admin/**”).hasRole(“ADMIN”)
.antMatchers(“/db/**”).hasRole(“DBA”)
.antMatchers(“/user/**”).hasRole(“USER”)
.and()
.formLogin()
.loginProcessingUrl(“/login”).permitAll()
.and()
.csrf().disable();
}
}
4.8、控制层
根据配置类编写不同权限的接口:
@RestController
public class HelloController {
@GetMapping(“/user/hello”)
public String userHello(){
return “你好,普通用户!”;
}
@GetMapping(“/admin/hello”)
public String adminHello(){
return “你好,管理员!”;
}
@GetMapping(“/db/hello”)
public String dbaHello(){
return “你好,数据库管理员!”;
}
}
启动项目,就可以访问不同权限的接口进行测试了。
在上面的实例中中定义了三种角色,但是这三种角色之间不具备任何关系,一 般来说角色之间是有关系的,例如 ROLE_ADMIN 一般既具有 ADMIN 的权限,又具有 USER 的权限。那么如何配置这种角色继承关系呢?在 pring Security 中只需要开发者提供一个 RoleHierarchy 即可。
假设 ROLE_DBA是终极大 Boss ,具有所有的权限, ROLE_ADMIN具有 ROLE_USER权限, ROLE_USER是一个公共角色,即 ROLE_ADMIN继承 ROLE_USER, ROLE_DBA继承ROLE_ADMIN ,要描述这种继承关系,只需要开发者在 Spring Security 的配置类中提供RoleHierarchy 即可,代码如下:
@Bean
RoleHierarchy roleHierarchy(){
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
String hireachy = “ROLE_DBA > ROLE_ADMIN> ROLE_USER”;
roleHierarchy.setHierarchy(hireachy);
return roleHierarchy;
}
配置完 RoleHierarchy 之后,具有 ROLE_DBA 角色的用户就可以访问所有资源了, 具有
ROLE_ADMIN 角色的用户也可以访问具有 ROLE_USER 角色才能访问的资源。
使用 ttpSecurity 配置的认证授权规则还是不够灵活,无法实现资源和角色之间的动态调整,要实现动态配置 URL 权限,就需要我们自定义权限配置,在第4节的基础上进行改造。
5.2.1、数据库设计
这里的数据库在4数据库的基础上再增加一张资源表和资源角色关联表,资源表中定义了用户能够访问的 URL 模式,资源角色表则定义了访问该模式的 URL 需要什么样的角色。
添加两张表之后的数据库表结构如下:
创建资源表和资源角色关联表并插入一些测试数据:
DROP TABLE IF EXISTS menu
;
CREATE TABLE menu
(
id
int(11) NOT NULL AUTO_INCREMENT,
pattern
varchar(50) NOT NULL COMMENT ‘路径’,
PRIMARY KEY (id
)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT=‘资源表’;
– Records of menu
INSERT INTO menu
VALUES (‘1’, ‘/db/**’);
INSERT INTO menu
VALUES (‘2’, ‘/admin/**’);
INSERT INTO menu
VALUES (‘3’, ‘/user/**’);
– Table structure for menu_role
DROP TABLE IF EXISTS menu_role
;
CREATE TABLE menu_role
(
id
int(11) NOT NULL AUTO_INCREMENT,
mid
int(11) NOT NULL COMMENT ‘资源id’,
rid
int(11) NOT NULL COMMENT ‘角色id’,
PRIMARY KEY (id
)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT=‘资源_角色关联表’;
– Records of menu_role
INSERT INTO menu_role
VALUES (‘1’, ‘1’, ‘2’);
INSERT INTO menu_role
VALUES (‘2’, ‘2’, ‘1’);
INSERT INTO menu_role
VALUES (‘3’, ‘3’, ‘3’);
5.2.3、实体类、持久层接口和映射文件
Menu.java:
public class Menu {
private Integer id;
private String pattern;
private List roles;
//省略getter、setter
}
MenuMapper.java:
@Mapper
public interface MenuMapper {
List
}
MenuMapper.xml:
<?xml version="1.0" encoding="UTF-8"?>SELECT
m.*,
r.id AS rid,
r.rolename AS rolename,
r.note AS note
FROM
menu m
LEFT JOIN menu_role mr ON m.id=mr.mid
LEFT JOIN role r ON mr.rid= r.id
5.2.3、自定义 FilterlnvocationSecurityMetadataSource
要实现动态配置权限,首先要自定义 FilterlnvocationSecurityMetadataSource,Spring Security中通过FilterlnvocationSecurityMetadataSource接口的getAttributes方法来确定一个请求需要哪些角色,接口的默认实现类是DefaultFilterlnvocationSecurityMetadataSource,参考DefaultFilterlnvocationSecurityMetadataSource,可以自定义FilterlnvocationSecurityMetadataSource接口实现类。
@Component
public class CustomFilterinvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
//创建一个AntPathMatcher实例,主要用来实现ant风格的URL匹配。
AntPathMatcher antPathMatcher =new AntPathMatcher() ;
@Autowired
MenuMapper menuMapper;
/**
-
@param o
-
参数是一个FilterInvocation,可以从中去除请求的url
-
@return Collection:表示当前请求 URL 所需的角色。
-
@throws IllegalArgumentException
*/
@Override
public Collection getAttributes(Object o) throws IllegalArgumentException {
//从FilterInvocation 中提取出当前请求的url
String requestUtl=( (FilterInvocation) o).getRequestUrl();
//从数据库中取出资源信息
List
for (Menu menu:menus) {
if (antPathMatcher.match(menu.getPattern(),requestUtl)){
//获取当前请求的 URL 所需要的角色信息
List roles=menu.getRoles();
String[] roleArr =new String[roles.size()];
for (int i=0;i<roleArr.length;i++){
roleArr[i] = roles.get(i).getRolename();
}
//返回角色信息
return SecurityConfig.createList(roleArr);
}
}
//如果不存在匹配的角色信息,返回ROLE_LOGIN,即登录就可访问
return SecurityConfig.createList(“ROLE_LOGIN”);
}
/**
-
@return 返回所有定义好的权限资源, Spring Security 在启动时会校验
-
相关配置是否正确 ,如果不需要校验,那么该方法直接返回 null 即可
*/
@Override
public Collection getAllConfigAttributes() {
return null;
}
/**
-
@param aClass
-
@return 返回类对象是否支持校验
*/
@Override
public boolean supports(Class<?> aClass) {
return FilterInvocation.class.isAssignableFrom(aClass) ;
}
}
5.2.4、自定义 AccessDecisionManager
当一个请求走完FilterlnvocationSecurityMetadataSource的getAttributes方法之后,会
来到AccessDecisionManager类中进行角色信息的比对,自定义AccessDecisionManager类如下:
@Component
public class CustomAccessDecisionManager implements AccessDecisionManager {
/**
-
判断当前登录的用户是否具备当前请求 URL 所需要的角色信息,如果不具备,就抛出AccessDeniedException异常
-
@param authentication
-
参数1:当前登录用户的信息
-
@param o
-
参数2:Filterlnvocation对象,可以取当前请求对象等
-
@param collection
-
参数3:FilterlnvocationSecurityMetadataSource中getAttributes 方法的返回值,即当前请求URL所需的角色
-
@throws AccessDeniedException
-
@throws InsufficientAuthenticationException
*/
@Override
public void decide(Authentication authentication, Object o, Collection collection) throws AccessDeniedException, InsufficientAuthenticationException {
Collection <? extends GrantedAuthority> auths = authentication.getAuthorities();
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
最后
作为过来人,小编是整理了很多进阶架构视频资料、面试文档以及PDF的学习资料,针对上面一套系统大纲小编也有对应的相关进阶架构视频资料
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》,点击传送门即可获取!
@param o
-
参数2:Filterlnvocation对象,可以取当前请求对象等
-
@param collection
-
参数3:FilterlnvocationSecurityMetadataSource中getAttributes 方法的返回值,即当前请求URL所需的角色
-
@throws AccessDeniedException
-
@throws InsufficientAuthenticationException
*/
@Override
public void decide(Authentication authentication, Object o, Collection collection) throws AccessDeniedException, InsufficientAuthenticationException {
Collection <? extends GrantedAuthority> auths = authentication.getAuthorities();
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。[外链图片转存中…(img-RrvzNJQb-1711824022438)]
[外链图片转存中…(img-HsZduGMq-1711824022439)]
[外链图片转存中…(img-Zh78HwiA-1711824022439)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
最后
作为过来人,小编是整理了很多进阶架构视频资料、面试文档以及PDF的学习资料,针对上面一套系统大纲小编也有对应的相关进阶架构视频资料
[外链图片转存中…(img-Et00NiAU-1711824022440)]
[外链图片转存中…(img-T7WwanvD-1711824022440)]
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》,点击传送门即可获取!