【稻草人问答项目—Day02】Spring-Security 验证以及授权框架、使用Bcrypt算法加密、自定义登陆页面
Day01 项目:【稻草人问答项目—Day01】环境搭建、数据库的连接、LOMBOK框架、MYbatis Plus Generator 代码生成器
接着Day 01项目继续往下进行
一、Spring安全框架的概述以及使用
1. Spring安全框架概述
- 不是随随便便的一个人就可以访问我们的网站页面,获取数据,那样我们的网站会很不安全,容易被黑客攻击;为此我们要给我们的网站做一些安全措施
- Spring安全框架: Spring-Security,是Spring提供的安全管理框架,所谓安全管理指登录控制,权限管理,访问规则等一个网站的安全设置,Spring-Security可以实现初级程序员编写少量代码实现较高安全级别的程序,现在主流的安全框架除了Spring-Security还有Shiro
2.Spring安全框架初步使用
- 1、在我们portal项目中添加依赖
<!-- 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>
- 依赖一旦添加成功,当前网站的所有资源都会被Spring-Security保护起来了,必须输入用户名和密码来访问,默认的用户名是user,密码会在启动服务时随机生成,输出到控制台上,
- 如果不想使用默认的用户名和密码,可以在application.properties文件中设置:
# 配置Spring-Security的用户名和密码
spring.security.user.name=admin
spring.security.user.password=123123
- 但是采用明文密码有安全隐患,别人就可以直接在配置文件中看到密码:123123,所以我们要进行密码加密
二、Bcrypt算法加密
- 现在主流的加密算法:MD5,BCrypt,这里我们使用Bcrypt
- Spring-Security框架直接包含了BCrypt的加密对象,供我们直接使用
BCrpty加密
- 1、首先在测试类中编写代码运行Bcrtpy的加密代码
@Test
public void bcrypt(){
//这个类就是能够对字符串进行加密的类
BCryptPasswordEncoder encoder=
new BCryptPasswordEncoder();
//将字符串加密的方法encode
String pwd=encoder.encode("123456");
//输出加密之后的字符串
System.out.println(pwd);
//$2a$10$MMHXEOPzAmVoDVbEpfcu5Om8tWcmnhREAYgmIeVrxmJkzMKD/L4cS
//每次运行程序加密的结果是不同的,这是加密算法的“加盐”
//虽然每次生产的结果不同,但是一定能表示我们要加密的内容
}
结果:
- 补充:加密算法“加盐” 就是每次运行程序加密的结果是不同的,虽然每次生产的结果不同,但是一定能表示我们要加密的内容
- 2、验证一个字符串是否匹配一个加密结果
@Test
public void bcrypt(){
//这个类就是能够对字符串进行加密的类
BCryptPasswordEncoder encoder=
new BCryptPasswordEncoder();
//下面验证匹配结果
//要匹配的字符串
String str="123123";
//要验证的加密结果
String pwd="$2a$10$MMHXEOPzAmVoDVbEpfcu5Om8tWcmnhREAYgmIeVrxmJkzMKD/L4cS";
//开始验证
boolean b=encoder.matches(str,pwd);
System.out.println("是否匹配:"+b);
}
- 3、回到我们使用的Spring-Security框架,配置文件中修改密码如下:
# 配置Spring-Security的用户名和密码
spring.security.user.name=admin
# {bcrypt}不是密码内容,而是约定好的算法id
# Spring-Security会根据指定的算法id对密码进行验证
spring.security.user.password={bcrypt}$2a$10$MMHXEOPzAmVoDVbEpfcu5Om8tWcmnhREAYgmIeVrxmJkzMKD/L4cS
三、Spring-Security框架验证用户登录
- 实际开发中数据库保存密码,我们就必须想办法在java代码实现对用户名和密码的设置
也就是说: 用户登录信息后,Spring-Security会根据用户的登录信息在对应的数据库表查找,如果信息匹配则登陆成功,显示页面!
3.1 使用用户详情类认证数据
- Spring-Security框架认证数据库中用户的流程图
流程分析: Spring-Security框架提供了一个UserDetails的类,这个类中能够保存用户的用户名,密码,权限等重要信息;把用户输入的 username用户名传入到我们的业务层:UserServiceImpl,业务层调用持久层UserMapper 根据传入的username在数据库查找,最终返回的是封装好的 UserDetails对象(包含 用户名,密码,权限等)
下面我们根据上述流程开始实现
- 1、创建一个包security,在包中创建Spring-Security的配置类
//当前类是一个配置类
@Configuration
//启动Spring-Security 的权限管理功能
@EnableGlobalMethodSecurity(prePostEnabled = true)
//Spring-Security要求当前配置类继承一个父类来配置相关信息
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserDetailsServiceImpl userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
}
- 2、创建一个UserDetailsServiceImpl类来返回用户详情
//@Component 表示这个类必须要注入到spring容器中
@Component
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private IUserService userService;
//Spring-Security 框架是按照程序员实现的接口中方法
//loadUserByUsername方法是spring-security框架用户验证用户信息的方法
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
//调用连接数据库并且获得用户详情的业务逻辑层方法直接返回
return userService.getUserDetails(username);
}
}
- 3、业务层接口
public interface IUserService extends IService<User> {
//spring-security在验证登录时
//连接数据库返回用户详情业务的方法
public UserDetails getUserDetails(String username);
}
- 4、业务层实现类
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails getUserDetails(String username) {
//根据用户名查询用户信息
User user = userMapper.findUserByUsername(username);
if (user==null){
return null;
}
//根据用户id查询用户所有权限
List<Permission> ps = userMapper.findPermissionsByUserId(user.getId());
//把得到的权限转换为字符串格式 String[]
String[]auths = new String [ps.size()];
int i = 0;
for (Permission p:ps){
auths[i++] = p.getName();
}
//声明用户详情并且赋值
UserDetails u = org.springframework.security.core.userdetails.User
.builder()
.username(user.getUsername())
.password(user.getPassword())
.authorities(auths)
//1 :锁 0:不锁
.accountLocked(user.getLocked()==1)
//1:可用 0:不可用
.disabled(user.getEnabled()==0)
.build();
return u;
}
}
- 现在缺的就是我们的持久层: UserMapper,根据用户输入的用户名来查询密码和权限,这就牵扯到了多表查询,下面画图看看各表的关系,才可以写sql语句
- 持久层接口
@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\n" +
"FROM user u\n" +
"LEFT JOIN user_role ur ON u.id=ur.user_id\n" +
"LEFT JOIN role r ON r.id=ur.role_id\n" +
"LEFT JOIN role_permission rp ON r.id=rp.role_id\n" +
"LEFT JOIN permission p ON p.id=rp.permission_id\n" +
"WHERE u.id=#{id}")
List<Permission> findPermissionsByUserId(Integer id);
}
四、Spring-Security授权范围设置
- 现在我们的项目中所有的资源都需要登录之后才能访问,但是网站中并不是所有页面和资源都需要先登录,根据网站设计,我们可以允许某些页面不登录也能访问,这就需要在Spring-Security中设置访问限制规则SecurityConfig类中添加一个方法来实现
@Override
protected void configure(HttpSecurity http) throws Exception {
//这个方法是用于设置当前Spring-Security框架访问控制范围的
http.csrf().disable() //关闭防跨域攻击的功能
.authorizeRequests()
//设置放行url
.antMatchers(
"/index.html",
"/img/**",
"/js/*",
"/css/*",
"/bower_components/**"
).permitAll()
//设置除放行url之外所有请求都需要登录
.anyRequest().authenticated()
//设置登录方式为表单输入
.and().formLogin();
}
- 这个方法中设置了放行的页面,和所有静态资源,还可以设置放行的控制器方法路径等
五、设置自定义登录页面
- 自定义登录页面是我们自己创建的视图:login.html
步骤
- 1、它需要thymeleaf的支持,所以要添加thymeleaf的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
- 2、static文件夹中的login.html就是我们登录页面的代码,将它移动到templates文件夹下称为视图模板
- 3、我们需要编写Controller代码才能显示视图模板内容给浏览器,所有我们创建一个SystemController控制器类来编写显示视图的代码
@RestController
public class SystemController {
@GetMapping("/login.html")
public ModelAndView loginForm(){
return new ModelAndView("login");
}
}
- 4、我们需要在配置Spring-Security的配置信息中,明确要使用我们的login.html替换默认的登录页面
//放行规则:就是并不是访问所有的页面都需要登录,我们对这些页面进行设置
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()//关闭防跨域攻击
.authorizeRequests()
//设置放行url
.antMatchers(
"/index.html",
"/login.html",
"/img/**",
"/js/*",
"/css/*",
"/bower_components/**"
).permitAll()
//设置除了url放行之外的所有请求都需要登录
.anyRequest().authenticated()
//设置登陆方式为表单输入
.and().formLogin()
.loginPage("/login.html")//访问控制器
.loginProcessingUrl("/login")// /login是spring-security框架要求固定写法
.failureUrl("/login.html?error")
.defaultSuccessUrl("/index.html")
.and().logout()//登出
.logoutUrl("/logout")
.logoutSuccessUrl("/login.html?logout");
}