最近学习了下springserurity登录认证操作,记录下学习整合的过程:
框架:springboot+mybatis+springsecurity
1 新建一个页面,路径resource/html/hello.html,做一个访问它需要账号密码的功能。
修改了默认的访问页面资源路径为resource/html/ ,application.properies文件中
spring.thymeleaf.prefix=classpath:/html/
spring.thymeleaf.suffix=.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Hello World!</title>
</head>
<body>
<h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
<form th:action="@{/logout}" method="post">
<input type="submit" value="Sign Out"/>
</form>
</body>
</html>
2 引入springsecurity 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
3 配置需要认证的页面,新增配置类继承WebSecurityConfigurerAdapter并重写 configure(HttpSecurity http)方法和configure(AuthenticationManagerBuilder auth)
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//路径/和/home不需要访问控制
http
.authorizeRequests()
// 如果有允许匿名的url,填在下面,/和/home 放行
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
//自定义登录页面login.html
.formLogin()
.loginPage("/login").permitAll()
.and()
.logout().permitAll();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// TODO Auto-generated method stub
//inMemoryAuthentication 从内存中获取 //auth.inMemoryAuthentication().withUser("JACK").password("123").roles("ADMIN");
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()).withUser("JACK").
password(new BCryptPasswordEncoder().encode("123")).roles("USER");
}
}
一开始我用auth.inMemoryAuthentication().withUser(“JACK”).password(“123”).roles(“ADMIN”);来指定认证用户,没认证成功,而且IDEA有报错:java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id “null”,网上查了一下因为Spring security 5.0中新增了多种加密方式,也改变了密码的格式。官方文档
上面这段话的意思是说,现如今Spring Security中密码的存储格式是“{id}…………”。前面的id是加密方式,id可以是bcrypt、sha256等,后面跟着的是加密后的密码。也就是说,程序拿到传过来的密码的时候,会首先查找被“{”和“}”包括起来的id,来确定后面的密码是被怎么样加密的,如果找不到就认为id是null。这也就是为什么我们的程序会报错:There is no PasswordEncoder mapped for the id “null”。官方文档举的例子中是各种加密方式针对同一密码加密后的存储形式,原始密码都是“password”。
原文链接:https://blog.csdn.net/canon_in_d_major/article/details/79675033
4 自定义登录页面
<!doctype html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<meta charset="UTF-8" />
<title>login</title>
</head>
<body>
<div th:if="${param.error}">无效用户名和密码</div>
<div th:if="${param.logout}">已登出</div>
<form th:action="@{/login}" method="post">
<div>User Name: <input type="text" name="username" /></label></div>
<div>password: <input type="password" name="password" /></label></div>
<div><input type="submit" value="Sign In"/></div>
</form>
</body>
</html>
5 启动springboot应用,浏览器输入http://localhost:8081/hello,会跳转到登录认证界面http://localhost:8081/login,
输入正确后
这种是自己设定固定的密码,现实情况应该是从数据库查找账户密码,然后比对输入的。所以我们来改一下,Mbatis整合就不多说了,还有新建User类用来封数据也不提了:
1 在上面的基础上增加和修改,新建一个service ,实现UserDetailsService接口,方法主要作用是根据输入的名字String username,查找数据库的用户,返回带有返回数据库用户信息的org.springframework.security.core.userdetails.User
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
IUser user;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//从数据库中根据输入的名字查找用户,返回userinfo
User userinfo = user.findUserByName(username);
//把权限属性放到 SimpleGrantedAuthority类,由于一个用户可能有多个权限,因此放到List authorities
List<SimpleGrantedAuthority> authorities = new ArrayList<SimpleGrantedAuthority>();
authorities.add(new SimpleGrantedAuthority(userinfo.getRoot()));
//返回org.springframework.security.core.userdetails.User,把数据库查到的用户信息写入,返回作判断
org.springframework.security.core.userdetails.User user =
new org.springframework.security.core.userdetails.User(userinfo.getUser(),new BCryptPasswordEncoder().encode(userinfo.getPassword()),true,true,true,true,authorities);
return user;
}
}
2 配置类 :auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
这句指定上面的 实际UserDetailsService接口的类,new BCryptPasswordEncoder()是密码的加密方式,对应的上面的类new BCryptPasswordEncoder().encode(userinfo.getPassword()),传入一样的加密方式。
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
CustomUserDetailsService userDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
//路径/和/home不需要访问控制
http
.authorizeRequests()
// 如果有允许匿名的url,填在下面,/和/home 放行
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
//自定义登录页面login.html
.formLogin()
.loginPage("/login").permitAll()
.and()
.logout().permitAll();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// TODO Auto-generated method stub
// auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()).withUser("JACK").
// password(new BCryptPasswordEncoder().encode("123")).roles("USER");
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
}
@Controller
public class Mycontroller {
@Autowired
private IService service;
@RequestMapping(value = "/hh",method = RequestMethod.GET)
public String gotoHello(){
System.out.println("gotoHello");
return "haha";
}
}
1 修改配置类,添加拦截url的权限 .antMatchers("/hh").hasRole(“ADMIN”),这时候访问http://localhost:8081/hh 就需要登录认证成功 和 “ADMIN”权限才能访问
@Override
protected void configure(HttpSecurity http) throws Exception {
//路径/和/home不需要访问控制
http
.authorizeRequests()
// 如果有允许匿名的url,填在下面,/和/home 放行
.antMatchers("/", "/home").permitAll()
// "/hh" 权限需要有认证用户的权限限制ADMIN
.antMatchers("/hh").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
//自定义登录页面login.html
.formLogin()
.loginPage("/login").permitAll()
.and()
.logout().permitAll();
}
运行一下,报错,原因是定义的角色名称不匹配,路径权限规则匹配中配置的是:ADMIN ,自定义权限验证中就要配置用户的权限:ROLE_ADMIN 需要加上ROLE_开头
2 修改 ArrayList();
authorities.add(new SimpleGrantedAuthority(“ROLE_”+userinfo.getRoot()));
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//从数据库中根据输入的名字查找用户,返回userinfo
User userinfo = user.findUserByName(username);
//把权限属性放到 SimpleGrantedAuthority类,由于一个用户可能有多个权限,因此放到List authorities
List<SimpleGrantedAuthority> authorities = new ArrayList<SimpleGrantedAuthority>();
authorities.add(new SimpleGrantedAuthority("ROLE_"+userinfo.getRoot()));
//返回org.springframework.security.core.userdetails.User,把数据库查到的用户信息写入,返回作判断
org.springframework.security.core.userdetails.User user =
new org.springframework.security.core.userdetails.User(userinfo.getUser(),new BCryptPasswordEncoder().encode(userinfo.getPassword()),true,true,true,true,authorities);
return user;
}
成功。
到这里已经登录认证成功了,我再访问其它需要认证的资源时,不会再次跳转login页面再输入账户密码,因为他把用户信息暂存在session里。