2022/07/12 ChangYongHui
- 实现目的
- 在原有SpringBootMybats工程上增加SpringSecurity框架,实现用户登录验证
- 实现用户信息从已有的数据库读取。
- 实现与前台框架layui和Thymeleaf的融合
- 具体步骤
- 导入Maven依赖并测试运行情况
复制这一段到pom文件的“dependencies”里面就可以
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
然后直接运行一下你的站点,发现需要用户登录的提示,并输入默认user和控制台打印的密码就可以登录。这说明你的导入是成功的,并且SpringSercurity正常运行了。
- 导入SpringSecurity设置并测试
在SpringSecurity这里主要通过建立“SecurityConfig”类来实现设置,这个类要继承“WebSecurityConfigurerAdapter”。
重写下面两个方法:
- “protected void configure(AuthenticationManagerBuilder auth)”
这个方法主要是对用户对象的操作,包括了对象名、密码、权限范围、角色等。都可以设置。下面就贴上一段测试代码,本次只使用它来测试而已。
@Override
protected void configure(AuthenticationManagerBuilder auth)throws Exception{
auth.inMemoryAuthentication()
//todo Spring security 5.0中新增了多种加密方式,也改变了密码的格式。
//todo 要想我们的项目还能够正常登陆,需要修改一下configure中的代码。我们要将前端传过来的密码进行某种方式加密
//todo spring security 官方推荐的是使用bcrypt加密方式。
.passwordEncoder(new BCryptPasswordEncoder())//的加密下面有
.withUser("lc")//添加用户
//添加密码
.password(new BCryptPasswordEncoder().encode("lcks123"))
//添加权限
.authority("vip1")
.and()
.withUser("root")
.password(new BCryptPasswordEncoder().encode("123456"))
.authority("vip1");
}
加密:
@Bean PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
到最后我们实际上建立了以数据端用户做为验证数据基础的机制以后代码会是这样的:
@Override
protected void configure(AuthenticationManagerBuilder auth)throws Exception{
auth.userDetailsService(userService);
}
- “protected void configure(HttpSecurity http)”
这个方法里面主要设置:
- 排除路径
- 用户访问站点自动跳转页面
- 权限访问路径
- 跨站跨域访问限制
- 登录表单设置
- Iframe框架限制
代码如下:
protected void configure(HttpSecurity http) throws Exception {
//下面的代码因为是使用角色来进行权限划分的,这里我们没用到
// http.authorizeRequests() // 验证请求
// // 路径匹配,参数是要处理的 url
// .antMatchers("/admin/**").hasRole("admin") // 要具有某种权限
// .antMatchers("/user/**").hasAnyRole("admin", "user")// 要具有某种权限中的一种
// .anyRequest().authenticated();
http
.authorizeRequests().antMatchers("/login", "/", "/toLogin", "/filedownload/**", "/static/**", "/*.html", "/layui/**")//排除路径,是未登录前的。
.permitAll()
.antMatchers("/index").hasAuthority("vip1")//登录之后可以任意访问的路径
.anyRequest().authenticated()
.and()
.csrf().disable();//跨站跨域访问限制解除,否则登陆之后无法跳转
http
.formLogin()//表示其他需要认证的请求通过表单认证
// .usernameParameter("username")//这里可以设置登录表单的验证字段
// .passwordParameter("password")
.loginPage("/")//如果用户访问站点就自动跳转这个地址,在这里记得去Controll设置空页面跳转登录页面只要将访问页面设置的是这个地址就可以了
.loginProcessingUrl("/login") // 登陆表单提交请求
// .successForwardUrl("/index");//认证成功 forward 跳转路径
.defaultSuccessUrl("/index")//访问指定页面,用户未登入,跳转至登入页面,如果登入成功,跳转至用户访问指定页面,用户访问登入页面,默认的跳转页面
// 登录失败的处理器
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
Map<String, Object> map = new HashMap<>();
map.put("status", 401);
if (e instanceof LockedException) {
map.put("msg", "账户被锁定,登录失败!");
} else if (e instanceof BadCredentialsException) {
map.put("msg", "用户名或密码输入错误,登录失败!");
} else if (e instanceof DisabledException) {
map.put("msg", "账户被禁用,登录失败!");
} else if (e instanceof AccountExpiredException) {
map.put("msg", "账户过期,登录失败!");
} else if (e instanceof CredentialsExpiredException) {
map.put("msg", "密码过期,登录失败!");
} else {
map.put("msg", "登录失败!");
}
out.write(new ObjectMapper().writeValueAsString(map));
out.flush();
out.close();
}
});
//开启自动配置的注销的功能
// /logout 注销请求
http.logout()// 注销成功的处理器
.logoutSuccessHandler(new LogoutSuccessHandler() {
@Override
public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
Map<String, Object> map = new HashMap<>();
map.put("status", 200);
map.put("msg", "注销登录成功!");
out.write(new ObjectMapper().writeValueAsString(map));
out.flush();
out.close();
}
})
.logoutSuccessUrl("/"); //注销成功来到首页;
// http.logout().logoutSuccessUrl("/");
//定制记住我的参数!
http.rememberMe().rememberMeParameter("remember");
// 解决不允许显示在iframe的问题
http.headers().frameOptions().disable();
http.headers().cacheControl();
}
注意:
- 记得去Controll设置空页面跳转登录页面只要将访问页面设置的是这个地址就可以了
- 注销的功能直接就是站点地址后面加logout,就可以了。
- 建立UserPojo对象,建立UserService。
由于本次要使用现有的数据库中的用户表对象来转化SpringScurity所要求的对象。所以除了建立符合规范的UserPojo。还要在UserService里面进行转化。
- UserPojo只包含以下属性:
private Integer id;
private String username;
private String password;
- UserService要继承UserDetailsService,并实现以下方法:
public UserDetails loadUserByUsername(String username)
代码如下:
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("前端传过来的username:"+username);
StaffDict staffDict = new StaffDict();
staffDict.setEMP_NO(username);
Map<String,Object> param = new HashMap<>();
param.put("staffDict",staffDict);
// 根据账号去数据库中查询
StaffDict ResultstaffDict = staffDictMapper.findListByMap(param).get(0);//向数据库查询用户的信息
//如果数据库返回不是空值,也就是用户存在就继续,不然返回登录失败。
if(ResultstaffDict != null){
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
// 设置登录账号的角色
authorities.add(new SimpleGrantedAuthority("vip1"));
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
String PassWordCode =bCryptPasswordEncoder.encode(ResultstaffDict.getPassWord());
UserDetails user = new User(ResultstaffDict.getUSER_NAME(),PassWordCode,authorities);
return user;
}
// 返回null 默认表示账号不存在
return null;
}
这里主要就是从老的数据库中查数据,然后转换相关USER_NAME、PassWordCode和authorities写入符合SpringSecurity要求的UserDetails对象中。
注意:
这里要对PassWord进行加密,不然SpringSecurity是不认的。加密方法是直接使用BcryptPasswordEncoder进行加密。
- 注意事项
本笔记要针对已经熟练使用Springboot+mybates的用户,所以很多Spring里面的设置都没有详细说明
- 未尽事宜
本笔记只是简单的对用户的合法性进行了校验,并没有角色等复杂内容进行详细设定。能跑能运行而已。