Spring Security使用数据库登录认证授权
一、搭建项目环境
1、创建 RBAC五张表
RBAC,即基于角色的权限访问控制(Role-Based Access Control),就是用户通过角色与权限进行关联。
在这种模型中,用户与角色之间,角色与权限之间,一般者是多对多的关系。
在 MySQL数据库中,创建如下几个表:
DROP TABLE IF EXISTS sys_user; CREATE TABLE sys_user( id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键id' , username VARCHAR(60) COMMENT '用户名' , password VARCHAR(255) COMMENT '密码' , status tinyint(1) COMMENT '用户状态,1-开启-0禁用' , password_non_expired tinyint(1) COMMENT '密码是否失效,1-可用,0-失效' , PRIMARY KEY (id) ) COMMENT = '用户表'; DROP TABLE IF EXISTS sys_role; CREATE TABLE sys_role( id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键id' , role_name VARCHAR(64) NOT NULL COMMENT '角色名' , role_desc VARCHAR(64) NOT NULL COMMENT '角色描述' , PRIMARY KEY (id) ) COMMENT = '角色表'; DROP TABLE IF EXISTS sys_permission; CREATE TABLE sys_permission( id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键id' , parent_id BIGINT COMMENT '父id' , permission_name VARCHAR(64) COMMENT '菜单名称' , permission_url VARCHAR(255) COMMENT '菜单地址' , PRIMARY KEY (id) ) COMMENT = '权限表'; DROP TABLE IF EXISTS sys_user_role; CREATE TABLE sys_user_role( id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键id' , user_id BIGINT NOT NULL COMMENT '用户id' , role_id BIGINT COMMENT '角色id' , enabled tinyint(1) DEFAULT 1 COMMENT '是否有效' , PRIMARY KEY (id) ) COMMENT = '用户角色关联表'; DROP TABLE IF EXISTS sys_role_permission; CREATE TABLE sys_role_permission( id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键id' , role_id BIGINT NOT NULL COMMENT '角色id' , permission_id BIGINT COMMENT '权限id' , PRIMARY KEY (id) ) COMMENT = '角色权限表';
2、创建项目
创建 Mavne 项目,Springboot + Spring Security + MyBatis + MySQL + jsp。
1)配置文件如下
server: port: 9090 # jsp配置 spring: mvc: view: prefix: /pages/ suffix: .jsp datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/security_authority?useUnicode=true;characterEncoding=utf8;useSSL=true;serverTimezone=GMT username: root password: 123456 # mybatis配置 mybatis: configuration: map-underscore-to-camel-case: true mapper-locations: classpath:mybatis/mapper/*.xml logging: level: com.charge.learn.springsecurity.springboot.security.jsp.dao: debug
2)启动类
@SpringBootApplication @MapperScan("com.charge.learn.springsecurity.springboot.security.jsp.dao") public class SpringSecurityApplication { public static void main(String[] args) { SpringApplication.run(SpringSecurityApplication.class, args); } }
二、整合 Spring Security实现用户认证
1、后端整合
1.1 用户
Spring Security的用户对象是 UserDetail类型
,Spring Security在认证流程中只认 UserDetail用户。
- 通过
UserDetailsService
的 loadUserByUsername方法获取 UserDetail用户。 - 认证成功之后,调用的是这个带有三个参数的 UsernamePasswordAuthenticationToken构造方法,将 角色信息添加到了
ArrayList<GrantedAuthority>
集合中。 - 在 successfulAuthentication 方法中,将认证信息存储到了SecurityContext中。
UserDetail接口的方法(根据用户业务来处理这几个值)。
- boolean enabled 账户是否可用
- boolean accountNonExpired 账户是否失效
- boolean credentialsNonExpired 账户密码是否失效
- boolean accountNonLocked 账户是否锁定
- Collection<? extends GrantedAuthority> getAuthorities() 获取账户的所有权限(用户角色)
注意:
四个布尔类型的参数都为 true时,然后成功,否则,有一个为 false,就会认证失败。
所以,我们可以将我们的用户封装成 UserDetail对象。
这里我们让用户对象实现 UserDetail接口,那么我们用户就属于 UserDetail类型的用户,然后实现接口的方法。
public class SysUser implements UserDetails { private Long id; private String username; private String password; private Boolean status; //用户状态,1-开启-0禁用 private Boolean passwordNonExpired; //密码是否失效,1-可用,0-失效 /** * 用户关联的所有角色 */ private List<SysRole> roles = new ArrayList<>(); //get、set方法 //标记该字段不做json处理 @JsonIgnore @Override public Collection<? extends GrantedAuthority> getAuthorities() { return roles; } @JsonIgnore @Override public boolean isAccountNonExpired() { return true; } @JsonIgnore @Override public boolean isAccountNonLocked() { return true; } @JsonIgnore @Override public boolean isCredentialsNonExpired() { return passwordNonExpired == null ? false : passwordNonExpired; } @JsonIgnore @Override public boolean isEnabled() { return status == null ? false : status; } }
1.2 角色
Spring Security的权限对象是 GrantedAuthority
类型。通过它的值来实现权限管理的。
所以,我们让角色对象实现GrantedAuthority接口,那么我们角色就属于 GrantedAuthority类型,然后实现接口的方法。
上面用户可以直接将 角色添加到 Collection<? extends GrantedAuthority>集合中。
public class SysRole implements GrantedAuthority { private Long id; private String roleName; private String roleDesc; //get、set方法 //标记该字段不做json处理 @JsonIgnore @Override public String getAuthority() { return roleName; } }
1.3 SysUserService接受继承UserDetailsService类
Spring Security在认证流程中通过 UserDetailsService
的 loadUserByUsername方法获取 UserDetail用户。
所以,我们让 UserService接口继承 UserDetailsService类
,然后重写 loadUserByUsername方法。
在 loadUserByUsername方法中,获取我们的用户信息( UserDetail类型),记得将用户关联的角色也赋值为用户信息。
public interface SysUserService extends UserDetailsService { void save(SysUser user); }
@Service @Transactional public class SysUserServiceImpl implements SysUserService { @Autowired private SysUserMapper sysUserMapper; @Autowired private SysRoleMapper sysRoleMapper; @Autowired private BCryptPasswordEncoder passwordEncoder; @Override public void save(SysUser sysUser) { // 将密码加密入库 sysUser.setPassword(passwordEncoder.encode(sysUser.getPassword())); sysUserMapper.insert(sysUser); } /** * 认证业务 * * @param username * - 用户在浏览器输入的用户名 * @return UserDetails - Spring Security的用户对象,返回 null表示认证失败! * @throws UsernameNotFoundException */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { /** * 用户信息和角色信息可以一步关联查询到位得到SysUser,我这里分开查询 */ // 1.查询用户 SysUser sysUser = sysUserMapper.getByUsername(username); if (sysUser == null) { return null; } // 2.获取用户关联的所有角色 List<SysRole> sysRoles = sysRoleMapper.listAllByUserId(sysUser.getId()); sysUser.setRoles(sysRoles); System.out.println("====> sysUser=" + sysUser.toString()); return sysUser; } }
mapper.xml中的几个方法
<select id="getByUsername" resultMap="BaseResultMap"> select id, username, password, status, password_non_expired from sys_user where username = #{username} </select> <select id="listAllByUserId" resultMap="BaseResultMap"> SELECT r.id, r.role_name role_name, r.role_desc role_desc FROM sys_role r, sys_user_role ur WHERE r.id = ur.role_id AND ur.user_id = #{userId} </select>
1.4 创建 SpringSecurity配置类
自定义一个配置类,添加@EnableWebSecurity注解
,并继承WebSecurityConfigurerAdapter类
。然后就拥有了 SpringSecutiry的所有默认配置。我们也可以修改配置。
@Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private SysUserService userService; // 加密对象注入IOC容器 @Bean public BCryptPasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } // 1.指定认证对象的来源(内存或者数据库),指定加密方式 @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userService).passwordEncoder(passwordEncoder()); } //2. SpringSecurity配置相关信息 @Override public void configure(HttpSecurity http) throws Exception { // 释放静态资源,指定拦截规则,指定自定义的认证和退出页面,csrf配置等 http.authorizeRequests() // 指定拦截规则 .antMatchers("/login.jsp", "failer.jsp", "/css/**", "/img/**", "/plugins/**").permitAll() //释放这些资源,不拦截 .antMatchers("/**").hasAnyRole("USER", "ADMIN") //所有资源都需要这些角色中的一个 .anyRequest().authenticated() //其他请求,必须认证通过之后才能访问 .and() // 表示新的一个配置开始 // 指定自定义的认证页面 .formLogin() .loginPage("/login.jsp") .loginProcessingUrl("/login") .successForwardUrl("/index.jsp") .failureForwardUrl("/failer.jsp") .permitAll() // 释放这些资源,不拦截登录 .and() // 指定自定义的退出页面 .logout() .logoutSuccessUrl("/logout") .invalidateHttpSession(true) // 清楚session .logoutSuccessUrl("/login.jsp") .permitAll() //.and() // 禁用csrf配置,默认开启的(一般不写,页面要加csrf),这里我们测试下 // .and() // .csrf() // .disable() ; }
主要配置信息如下:
- 指定认证对象 SysUserService (UserDetailsService类型)
- 指定了用户密码使用的加密对象
- SpringSecurity配置相关信息,比如:指定拦截规则,指定自定义的认证页面,csrf等。
2、前端整合
在 Spring Security 中,如果我们不做任何配置,默认的登录页面和登录接口的地址都是 /login,即默认会存在如下两个请求:
- GET http://localhost:8080/login
- POST http://localhost:8080/login
如果是 GET 请求表示你想访问登录页面,如果是 POST 请求,表示你想提交登录数据。默认的表单字段为 username和password。
SpringSecurity 默认 是开启 csrf防护机制。
所以,在自定义的表单上添加上 _csrf隐藏input(必须要写在form表单里面)。
引入 security标签库 <%@taglib uri="http://www.springframework.org/security/tags" prefix="security"%> <security:csrfInput/>
启动项目,登录认证访问ok.
三、整合 Spring Security实现用户授权
认证过程获取用户信息时,我们已经把用户关联的角色信息设置到了 UserDetails中,所以,我们只需要分配 资源访问的角色就可以了。
1、后端
1.1 开启 Spring Security权限控制
Spring Security可以通过注解的方式来控制类或者方法的访问权限。支持开启权限控制的注解类型如下:
- jsr250-annotations:表示支持 jsr250-api的注解
- pre-post-annotations:表示支持 spring表达式注解
- secured-annotations:这才是 Spring Security提供的注解
在实际开发中,用一类即可,三个都开启也没关系。
在 SpringSecurity配置类上 添加 @EnableGlobalMethodSecurity注解
,表示开启 Spring Security权限控制,这里我们三类都开启了。
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(securedEnabled=true, prePostEnabled = true, jsr250Enabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { ...
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(securedEnabled=true, prePostEnabled = true, jsr250Enabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { ...
1.2 在角色对应类或者方法上添加权限注解
1.2.1 使用 Spring Security注解
- @Secured({“ROLE_ADMIN”,“ROLE_PRODUCT”})
@Controller @RequestMapping("/user") @Secured("ROLE_ADMIN") //表示当前类中所有方法需要 ROLE_ADMIN才能访问 public class UserController { @Autowired private UserService userService; @RequestMapping("/findAll") public String findAll(Model model){ List<SysUser> list = userService.findAll(); model.addAttribute("list", list); return "user-list"; } 。。。 }
1.2.2 使用 Spring表达式注解
- @PreAuthorize(“hasAnyRole(‘ROLE_ADMIN’,‘ROLE_PRODUCT’)”)
@Controller @RequestMapping("/product") public class ProductController { @RequestMapping("/findAll") //表示当前类中findAll方法需要 ROLE_ADMIN或者 ROLE_PRODUCT才能访问 @PreAuthorize("hasAnyRole('ROLE_ADMIN','ROLE_PRODUCT')") public String findAll(){ return "product-list"; } }
1.2.3 使用 JSR-250注解
- @RolesAllowed({“ROLE_ADMIN”,“ROLE_User”})
@Controller @RequestMapping("/order") @RolesAllowed({"ROLE_ADMIN","ROLE_USER"}) //表示当前类中所有方法都需要ROLE_ADMIN或者ROLE_User才能访问 public class OrderController { @RequestMapping("/findAll") public String findAll(){ return "order-list"; } }
2、前端
在jsp业页面中,对每个菜单资源通过 SpringSecurity标签库指定访问所需的角色。
<%@taglib uri="http://www.springframework.org/security/tags" prefix="security" %> <!-- 指定访问所需角色 --> <security:authorize access="hasAnyRole('ROLE_ADMIN', '等等')">
启动项目,通过不同的用户登录,授权访问ok.
到此这篇关于Spring Security使用数据库登录认证授权的文章就介绍到这了,更多相关Spring Security数据库登录认证授权内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
Windows系统下Java连接SQL Server的方法简介
这篇文章主要介绍了Windows系统下Java连接SQL Server的方法,分别是JDBC和JTDS的相关使用,需要的朋友可以参考下2015-09-09Idea报错: A JNI error has occurred的问题
这篇文章主要介绍了Idea报错: A JNI error has occurred的问题及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教2023-08-08Java中的ThreadLocal与ThreadLocalMap详解
这篇文章主要介绍了Java中的ThreadLocal与ThreadLocalMap详解,ThreadLocal 是一个线程局部变量,其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,需要的朋友可以参考下2023-09-09
最新评论