Spring 安全框架
什么是Spring安全框架
Spring-Security(Spring安全框架)是Spring提供的安全管理组件
是Spring框架环境下提供的安全管理和权限管理的组件
一个项目一般都会有登录功能,我们之前编写的登录功能非常简陋,不能用于实际开发
Spring-Security提供了专业的实现登录的方式,供我们使用
使用Spring-Security实现登录
基本使用
步骤1:
导入依赖
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Spring Security Test -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
步骤2:启动服务
在启动服务的控制台信息中会包含一个随机产生的密码
用于登录Spring-Security框架
用默认的用户名是"user"
密码赋值控制台出现的,粘贴登录即可
步骤3:
我们不想用它提供的用户名和密码,就可以自己设置
application.properties文件中添加配置如下
# Spring-Security配置用户名密码
spring.security.user.name=admin
spring.security.user.password=123456
步骤4:
密码加密
实际开发中,一定不可能在任何位置保存明文密码
将用户的密码经过加密之后再保存,能够最大限度的提高密码的安全级别
而加密的过程不需要我们编写,使用现成的加密规则即可
我们使用BCrypt的加密规则
首先我们新建一个包,cn.tedu.straw.portal.security
配置类SecurityConfig,在这个类中注入加密对象
代码如下
//@Configuration表示当前类是配置类,可能向Spring容器中注入对象
@Configuration
public class SecurityConfig {
//注入一个加密对象
@Bean
public PasswordEncoder passwordEncoder(){
//这个加密对象使用BCrypt加密内容
return new BCryptPasswordEncoder();
}
}
步骤5:
下面进行测试,测试加密功能和验证功能
代码如下
@SpringBootTest
public class SecurityTest {
@Autowired
PasswordEncoder passwordEncoder;
@Test
public void encodeTest(){
/*
每次运行加密结果不同
是因为加密对象采用了"随机加盐"技术,提高安全性
*/
String pwd=passwordEncoder.encode("123456");
System.out.println(pwd);
//$2a$10$IHMiKBqpiPFYgRg4P0E0HeU.xdkr1nw0/y1AWKIvHh5TMNwxVuBRW
}
@Test
public void matchTest(){
/*
验证我们输入的密码是不是能匹配生成的密文
*/
boolean b=passwordEncoder.matches("123456",
"$2a$10$IHMiKBqpiPFYgRg4P0E0" +
"HeU.xdkr1nw0/y1AWKIvHh5TMNwxVuBRW");
System.out.println(b);
}
}
步骤6:
修改application.properties文件中配置的密码
# Spring-Security配置用户名密码
spring.security.user.name=admin
spring.security.user.password=$2a$10$IHMiKBqpiPFYgRg4P0E0HeU.xdkr1nw0/y1AWKIvHh5TMNwxVuBRW
测试登录…
步骤7:
上面的操作可以简化
将一个Spring内置的算法标记标注在application.properties文件的密文密码前
代码如下
# Spring-Security配置用户名密码
spring.security.user.name=admin
spring.security.user.password={bcrypt}$2a$10$IHMiKBqpiPFYgRg4P0E0HeU.xdkr1nw0/y1AWKIvHh5TMNwxVuBRW
安照上面的配置,我们刚刚注入的PasswordEncoder对象就可以省略了
//注入一个加密对象(密码中设定了算法ID下面的注入就省略了!)
/*@Bean
public PasswordEncoder passwordEncoder(){
//这个加密对象使用BCrypt加密内容
return new BCryptPasswordEncoder();
}*/
权限管理
有些网页必须登录之后才能访问
即使都是登录用户,它们也有可能有身份的区别,不同身份有不同功能
只有学生才能访问发布问题的页面,
只有老师才能访问回答问题的页面
Spring-Security也方便我们来管理这些问题
下面我们就使用Spring-Security权限管理功能访问用户信息
步骤1:开启权限管理功能
SecurityConfig这类中编写代码
//@Configuration表示当前类是配置类,可能向Spring容器中注入对象
@Configuration
//下面的注解表示通知Spring-Security开启权限管理功能
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends
WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
//方法中配置哪个用户可以有什么样的权限
auth.inMemoryAuthentication().withUser("Tom")
.password("{bcrypt}$2a$10$IHMiKBqpiPFYgRg4P0E0HeU.xdkr1nw0/y1AWKIvHh5TMNwxVuBRW")
.authorities("/tag/get");
}
}
步骤2:
在Controller类的方法上声明访问这个方法需要什么权限
如果访问这个方法需要"/tag/get"权限,那么没有这个权限的人就不能访问
如果Controller类的方法上没有声明权限,那么就是登录的用户都可以访问
在TagController类中编写一些权限
代码如下
@RestController
//下面的注解表示想访问本控制器中的任何方法需要前缀/portal/tag
@RequestMapping("/portal/tag")
public class TagController {
@Autowired
private IUserService userService;
@Autowired(required = false)
private ITagService tagService;
@GetMapping("/get")//localhost/portal/tag/get?id=3
@PreAuthorize("hasAuthority('/tag/get')")
public User get(Integer id){
return userService.getById(3);
}
// 编写一套可以按tagid查询一个tag信息的控制器方法
// 将tag信息显示在浏览器
@GetMapping("/gettag")
@PreAuthorize("hasAuthority('/tag/byid')")
public Tag getTag(Integer id){
return tagService.getById(id);
}
}
测试运行
tom正常访问/get?id=3
访问/gettag?id=14时发生403错误,表示权限不足
现在Tom用户不能访问的localhost/portal/tag/gettag路径,因为他没有权限
我们可以通过配置将这个权限赋予Tom用户
方法如下
@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
//方法中配置哪个用户可以有什么样的权限
auth.inMemoryAuthentication().withUser("Tom")
.password("{bcrypt}$2a$10$IHMiKBqpiPFYgRg4P0E0HeU.xdkr1nw0/y1AWKIvHh5TMNwxVuBRW")
.authorities("/tag/get","/tag/byid");
}
使用UserDetailsService提供认证数据
我们service包中编写一个类UserDetailsServiceImpl
代码如下
/**
* 这个类实现UserDetailsService接口
* 为Spring-Security提供认证的数据
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
//Spring-Security认证信息时
//会将用户名传递到这个方法中
//根据这个用户名获得数据库中加密的密码,
//如果匹配则登录成功
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
//这个方法返回的是用户的详细信息
UserDetails user=null;
//暂时规定正确的用户名和密码是"Jerry",加密的"123456"
if("Jerry".equals(username)){
//如果用户名正确,将加密的密码保存到User对象
//下面的User类也是Spring提供的不是我们的实体类
user= User.builder()
.username("Jerry")
.password("{bcrypt}$2a$10$IHMiKBqpiPFYgRg4P0E0HeU.xdkr1nw0/y1AWKIvHh5TMNwxVuBRW")
//这里也能直接赋予权限
.authorities("/tag/get")
.build();
}
System.out.println("user:"+user);
return user;
}
}
回到SecurityConfig类中,删除configer方法之前的代码
将方法改写为:
//@Configuration表示当前类是配置类,可能向Spring容器中注入对象
@Configuration
//下面的注解表示通知Spring-Security开启权限管理功能
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends
WebSecurityConfigurerAdapter {
@Autowired
UserDetailsServiceImpl userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.userDetailsService(userDetailsService);
}
}
关于用户的权限
数据库中有:
permission表,保存权限
role表,保存角色
role_permission表,保存角色和权限的关系
role是permission多对多关系,多对多关系的表一定会出现一张中间表,来保存他们的关系
user表,保存用户信息
user_role表,保存用户和角色的关系
user和role表也是多对多的关系
我们在登录用户时需要指定用户的权限,根据用户的id查询权限可能需要使用这5张表的连接查询
除了对权限的查询外,还需要用户的基本信息,使用用户名查询出用户对象即可
在UserMapper接口中添加如下两个查询
@Repository
public interface UserMapper extends BaseMapper<User> {
//根据用户输入的用户名查询用户信息的方法
@Select("select * from user where username=#{username}")
User findUserByUsername(String username);
//查询指定id的用户的所有权限
@Select("SELECT p.id,p.name" +
" FROM user u" +
" LEFT JOIN user_role ur ON u.id=ur.user_id" +
" LEFT JOIN role r ON r.id=ur.role_id" +
" LEFT JOIN role_permission rp ON r.id=rp.role_id" +
" LEFT JOIN permission p ON p.id=rp.permission_id" +
" WHERE u.id=#{id}")
List<Permission> findUserPermissionsById(Integer id);
}
最好还是测试一下,保证万无一失!
测试代码如下
@Autowired
UserMapper userMapper;
@Test
public void findUser(){
User user=userMapper.findUserByUsername("tc2");
System.out.println(user);
}
@Test
public void findPermissions(){
List<Permission> list=userMapper.findUserPermissionsById(3);
for(Permission p: list){
System.out.println(p);
}
}
测试成功!!!
连接数据库实现Spring-Security登录
步骤1:
在编写IUserService接口中添加一个获得用户详情的方法
public interface IUserService extends IService<User> {
//这个方法用法查询获得用户详情对象的业务
//UserDetails是SpringSecurity验证用户必要的信息
//String username是SpringSecurity接收的用户输入的用户名
UserDetails getUserDetails(String username);
}
步骤2:
在impl包下的UserServiceImpl类中实现这个方法
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Autowired
UserMapper userMapper;
@Override
public UserDetails getUserDetails(String username) {
//根据用户名获得用户对象
User user=userMapper.findUserByUsername(username);
//判断用户对象是否为空
if(user==null) {
//如果为空直接返回null
return null;
}
//如果不为空根据用户的id查询这个用户的所有权限
List<Permission> permissions=
userMapper.findUserPermissionsById(user.getId());
//将权限List中的权限转成数组方便赋值
String[] auths=new String[permissions.size()];
for(int i=0;i<auths.length;i++){
auths[i]=permissions.get(i).getName();
}
//创建UserDetails对象,并为他赋值
UserDetails ud= org.springframework.security.core.userdetails
.User.builder()
.username(user.getUsername())
.password(user.getPassword())
.accountLocked(user.getLocked()==1)//写==1是判断锁定
.disabled(user.getEnabled()==0)//写==0是判断不可用
.authorities(auths).build();
//最后返回UserDetails对象
return ud;
}
}
步骤3:测试代码
@Autowired
IUserService userService;
@Test
public void abcLogin(){
UserDetails ud=userService.getUserDetails("tc2");
System.out.println(ud);
}
步骤4:
UserDetailsServiceImpl类中来调用刚刚编写的UserServiceImpl类中的方法
返回UserDetails对象即可
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
//Spring-Security认证信息时
//会将用户名传递到这个方法中
//根据这个用户名获得数据库中加密的密码,
//如果匹配则登录成功
@Autowired
IUserService userService;
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
return userService.getUserDetails(username);
}
}
控制授权范围
网站有些页面需要登录后才能访问,但是有些直接就可以访问
我们设置一下授权范围,无论是否登录都可以访问首页
代码如下
//@Configuration表示当前类是配置类,可能向Spring容器中注入对象
@Configuration
//下面的注解表示通知Spring-Security开启权限管理功能
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends
WebSecurityConfigurerAdapter {
@Autowired
UserDetailsServiceImpl userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.userDetailsService(userDetailsService);
}
//控制授权代码在这里!!!!!
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()//对当前全部请求进行授权
.antMatchers(
"/index.html",
"/img/*",
"/js/*",
"/css/*",
"/bower_components/**"
)//设置路径
.permitAll()//允许全部请求访问上面定义的路径
//其它路径需要全部进行表单登录验证
.anyRequest().authenticated().and().formLogin();
}
随笔
123456
234567
ACDCB
BDADC
开发顺序: mapper->service->controller->html
逆向开发:html->controller->service->mapper
以后编写Service一般都是先在对应Service的接口中添加方法
再找到对应接口的实现类编写代码