开发者学堂课程【Spring Security知识精讲与实战演示(三):集中式整合之使用数据库数据实现认证】学习笔记与课程紧密联系,让用户快速学习知识
课程地址:https://developer.aliyun.com/learning/course/732/detail/13064
集中式整合之使用数据库数据实现认证
内容介绍:
一、提供对象
二、接口
三、提供用户对象和角色对象
四、指定关系
五、业务逻辑的编写
六、测试
一、提供对象
前面已经在整合的环境中加入了通用mapper的环境,接下里将使用通用mapper来完成spring security的认证业务。
首先需要提供一个用户对象sysuser,此类方法的不同之处是将让user的对象直接实现user.details的接口。
接下里讲述里面按所使用的方法。Spring security内部只识别内部为user details的用户对象,并不识别用户自己定义的对象。此时需要用户自己定义的对象直接实现接口,那么以后spring security能够自动识别用户自己定义的对象。也可以先让用户定义的对象不与spring security产生关系,创建完成后,在手动完成该对象。
importorg. springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
public class SysUser implements UserDetails(
@0verride I
public Collection<? extends GrantedAuthority> getAuthorities() (return null;
)
@0verride
public String getPassword() (
return null;
集中式项 )
境 @0verride
public String getUsername() (
return null;
)
用户除了实现spring security规定的一些方法之外,还需要加入一些自定义的属性。
在数据库中,打开user,里面的id、username、password和status都属于自定义的属性,一一进行编写。
二、接口
首先引进一个integer类型的id,再编写一个string类型的username,保证属性名一致,紧接着编写一个string类型的password,最后还有一个integer类型的status,在里面还需要生成一个integer get的一个方法。
package com.itheima.domain;
Importorg.springframework.security.
core.GrantedAuthority; import
org. springframework. security. core.userdetails.UserDetails;
inport java.util.Collection;
public class SysUser implements UserDetails(
private Integer id;
private String username;
private String password;
private Integer status;I
public Integer getId()
return id;
public void setId(Integer id) (
this.id = id;
)
实现完接口之后,有许多需要重写的方法,下图中四个Boolean值,都是在指定当前用户是否可用。例如最底下的Boolean如果为false,则说明当前用户不可用,则可以将其改为true,上面几个同理改为true,那么就可以不再判断当前用户的状态,在里面的段相当于直接废弃掉。
Averride
public boolean isAccountNonExpired() return true
QOverride
public boolean isAccountNonLocked()( return true;
@0verride
public boolean isCredentialsNonExpired() (return true;
@0verride
public boolean isEnabled()
return true;
)
数据库需要定义,1是开启,0是关闭,当前类型为int类型,不能直接返回,如果是teger int,就可以直接将字符段返回,默认1为true,0为false
继续往上看会发现一个get方法,在里面需要得到当前角色信息,因为当前还没有角色对象,只能先空着这一步。Password直接返回password即可,username直接返回username。此时对象初步完成。
public void setId(Integer id)(
this.id = id;
public voidsetUsername(String username) (
this.username = username;
)
public voidsetPassword(String password) (
this.password = password;
I
public Integer getStatus() (
return status;
public voidsetStatus(Integer status) (
this.status = status;
)
三、提供用户对象和角色对象
此时初步完成步骤,还差一步角色信息,这里需要提供一个sysrole对象,可以效仿用户对象的做法,使其直接实现spring security规定的对象,将所需要的类型复制过来,将需要实现的方法实现,此处即一个get方法需要指定。
查看一下需要自定义的属性有哪些,打开角色表,发现有三个属性,ID、rolename和roledesc。
将其一一进行编写,写入一个integer类型的ID,一个string类型的rolename和string类型的roledesc,里面需要生成一个get方法,最后需要注意的是需要返回一个角色名称即rolename,用户对象有了之后,就可以在用户对象中添加集合属性:list<sysrole>roles,此处同样需要生成一个set get方法。生成完之后,就可以返回给role,到此这俩个对象差不多定义完成。
import java.util.Collection;
import java.util.List;
public class SysUser implements UserDetails (
private Integer id;
private String username;
private String password;
private Integer status;
private List<SysRole> rolgs;
public List<SysRole> getRoles()
return roles;
public void setRoles(List<SysRole> roles) (this.roles = roles;
public Integer getId() (
在此处需要注意的是,当前俩个对象,未来可能会转换成字符串或同间隔的字符串。如果涉及到这些操作的话,那么需要将重写的属性忽略掉。
操作如下:加上jason ignore注解说明,则证明get方法后面的字符串的属性就不再参与字符串的转换了。这一步需要加上,因为这并不属于当前对象的属性,而是规范的方法。同样,这些对象也需要加到user的对象中,需要进行重写。以及Boolean值都需要进行添加,这里的username和password虽然是重写值,但是不需要进行添加,因为自身也有username和password的属性。
return status;
public void setStatus(Integer status) (
this.status = status;
@0verride
public Collection<?extends GrantedAuthority> getAuthorities() (return roles;
@Override
public String getPassword(O (
return password;
@0verride
public String getUsername() (
return username;
到此步为止,重写的方法都已经忽略掉,用户对象和角色对象才算彻底完成。编写完成之后,需要编写对应的itheima mapper映射,在里面需要写入俩个接口:usermapper和角色mapper。在通用mapper中,这俩个接口都继承一个接口。在用户里面同样需要继承一个mapper,里面依然需要一个sysuser,可以提前写入一个提前可以查询的用户名方法: public sysuser findbyname(),可以使用插麦,也可以使用注解。在里面需要传入一个string类型的username。
接着需要加上select的注解:(select * from sys_user where username = #{}”)。需要注意的是用数据库查询的select * from sys _user,只是当前用户对象的属性但是当前需要分装的是需要角色,因此需要继续往下写:@Results(
@Result(id = true, property ="id", column ="id"),
接下来继续分装role的属性里面的 @Result(property ="roles”, column ,通过当前结果的某列来间接查询出角色对象ID,最终可以在里面指定需要分装的类型。例如想要分装list类型,即写入Javatype = list.class,
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
inport tk.mybatis. mapper.common. Mapper;
import java.util.List;
public interface UserMapper extends Mapper<SysUser>[
@Select("select *from sys_user where username =#(username)")
@Results(
@Result(id = true, property ="id", column ="id"),
@Result(property ="roles”, column =【id", javaType = List.class, many =Many(select=""))
])
public SysUser findByName(String username);
四、指定关系
接下来指定是一对一的关系还是一对多的关系,list是一对多的关系,指定一条嵌套查询的语句,即拿ID当条件查询出list的集合出来。
回到角色里面,需要再指定一个接口,public最终的返回值。写一个integer类型的ID,返回值为一个角色集合,所以在sysrole里面查询,如果只是在一张表中,加不了用户ID,因为在一张表中,没有用户ID。需要连查sys_user_role ur,让这俩张表产生关系。
1 SELECT ★ FROM sys role r, sys user role ur
2 WHEREr.id=ur.rid AND ur.uid=#(uid)
先看前面一段查询出来的结果,能不能直接分装到结果集中,可以看到sysrole的三个属性与结果集是否一一对应。首先ID是对应的,但是发现rolename和roledesc不一样,一个是下划线,一个是脱汞模式,因此需要改名字使其一致,需要修改ID,修改role name
修改roledesc。
换行之后:
1 SELECTr.id, r.role name roleName, r.role desc roleDesc
2 FROMsys role rrsysuser role ur
3 WHEREr.id=ur.rid AND ur.uid=#(uid)
这时就跟数据库需要分装的对象一致了,将com itheima.mapper.rolemapper.role.findbyuid复制粘贴到用户这边来。到此,mapper的运算文件编写完成。
五、业务逻辑的编写
Spring security要求业务逻辑必须是userdetails类型,首先创建一个接口user service,当前接口不能被spring security所识别。因此让自己创建的接口继承user details。继承完毕之后,编写实现类,在基础之上,创建一个impl包,在里面将接口实现。当前的user service对象必须放回到ioc容器中,所以在上面加上service注解。
因为当前是是业务层,可能会用到事务,所以加上事物说明,此处也可省略。
但是今后在里面如果写入大量的增删改查的方法,就需要用到事物的注解。在里面需要注入用户的user mapper。在里面的报错可以忽略掉,因为在注入对象的时候加上一个前面的注解。
此时在着这里加上一个扫描的通用mapper产生的代理对象,代理对象并不是真的有对象,是一个增强对象,因此在检测的时候会认为容器中无此对象。
接下里可以按照需求返回user details对象,这时自己定义的对象就已经是user details类型了,因此此处非常简单,此时会发现业务逻辑会变得特别简单。之前是将俩个对象对换出来变成spring security对象,此时直接让这对象变换成spring security。以上即业务逻辑的过程。
import org.springframework.stereotype.Service;
import
org. springframework.transaction.annotation.Transactional;
@Service
@Transactional
public class UserServiceImpl implements UserService(
@Autowired
private UserMapper userMapper;
@0verride
public
UserDetails
loadUserByUsername(String s) throws UsernamelNotFoundException (
return userMapper.findByName(s);
写完之后还不能直接使用,需要修改配置文件,之前在内存中保存的信息可以删除:auth.user details service (),这是需要一个对象,将user details service 注入进去。
接下里给user details 指定一个加密方式,加密方式在password里面。在此处需要在ioc逻辑中放入一个加密对象。
importorg.springframework. security.crypto.bcrypt.BCryptPasswordEncoder;
C@Configuration
EnableWebSecurity
public
class WebSecurityConfig extends WebSecurityConfigurerAdapter (
@Autowired
private UserService userService;
@Bean
public BCryptPasswordEncoder passwordEncoder()(
return new BCryptPasswordEncoder ();
//指定认证对象的来源
public void configure(AuthenticationManagerBuilder auth) throws Exception( auth.userDetailsService(userService).passwordEncoderQ;
//SpringSecurity配置信息
public void configure(HttpSecurity http) throws Exception (
六、测试
到此处可以让spring security自己使用数据库来实现数据认证操作。接下里做一个测试。
测试的时候选择topcat插件的方法测试。在浏览器输入localcost:8080。进入到自定义认证页面,在这里可以用数据库的认证对象。密码登录后,会发现可以访问成功,在里边也可以查看访问产品管理,也能出现。这就证明之前查看这些权限是需要user的角色。当前的用户id是4,它规定的id也为4。就证明user下面是有此角色的,即认证成功。
spring:
mVc:
view:
prefix:/pages/
suffix:.jsp
datasource:
driver-class-name:commysql.jdbc.Driver url:jdbc:mysql:///security_authority username:root
password:root
mybatis:
type-aliases-package:com itheima.domain configuration:
map-underscore-to-camel-case:true logging:
leve1:
com itheima:debug