一、使用Spring Security 导入依赖,改密码
Spring Security 6.x 的认证实现流程如下:
用户提交登录请求
1. Spring Security 将请求交给 UsernamePasswordAuthenticationFilter 过滤器处理。
2. UsernamePasswordAuthenticationFilter 获取请求中的用户名和密码,并生成一个 AuthenticationToken 对象,将其交给 AuthenticationManager 进行认证。
AuthenticationManager 通过 UserDetailsService 获取用户信息,然后使用 PasswordEncoder 对用户密码进行校验。
3. 如果密码正确,AuthenticationManager 会生成一个认证通过的 Authentication 对象,并返回给 UsernamePasswordAuthenticationFilter 过滤器。如果密码不正确,则 AuthenticationManager 抛出一个 AuthenticationException 异常。
4. UsernamePasswordAuthenticationFilter 将 Authentication 对象交给 SecurityContextHolder 进行管理,并调用 AuthenticationSuccessHandler 处理认证成功的情况。
5. 如果认证失败,UsernamePasswordAuthenticationFilter 会调用 AuthenticationFailureHandler 处理认证失败的情况。
二、用户自定义登录页面
依赖:
<!--配置security依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--jsp依赖-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
在application.yml中配置用户名密码
新建配置类WebSecurityConfig
package com.example.springboottest;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder(){
//Spring Security默认的密码加密器
return new BCryptPasswordEncoder();
}
/**
* spring security 配置的核心方法
* @param http 配置的核心对象
* @throws Exception 配置错误【抛出的异常】
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
//HTTP请求认证/鉴权配置信息
http.authorizeRequests()
//antMatchers: http请求匹配器,按照指定的patten进行匹配
//anonymous 匿名访问,只允许匿名状态时候访问,如果已经登录,则无法访问
.antMatchers("/login.jsp").anonymous()
//permitAll 赋予所有权限==没有权限的人可以访问
.antMatchers("/static/**").permitAll()
//anyRequest() 匹配上方配置外,其他所有请求
//authenticated() 认证访问,只有认证过的用户才能访问
.anyRequest().authenticated();
//登录表单相关的配置
http.formLogin()
//登录页面,当用户未登录,访问受保护的资源时,会跳转到该页面
.loginPage("/login.jsp")
//登录表单提交地址,即登录表单action地址,即处理登录请求的路径
.loginProcessingUrl("/login")
//登录成功后跳转的页面
.defaultSuccessUrl("/")
.usernameParameter("username") //表单中提交后用户名的参数名称 登录表单用户名参数名,默认为username
.passwordParameter("password");//表单中提交后用户名的参数名称 登录表单密码参数名,默认为password
}
}
然后自己创建login.jsp页面,这样就会使用自己自定义的登录页面。
有个问题,我们不会把用户从配置文件中写死,我们会从数据库中查找用户然后根据数据库匹配登录是否成功。
三、自定义获取用户认证/权限信息
UserDetails对象
在spring Security中,所有的用户最终认证后都会被封装为userDetails接口的实现类,该接口主要定义如下几个方法:
public interface UserDetails extends Serializable {
//返回认证用户的所有权限
Collection<? extends GrantedAuthority> getAuthorities();
//返回认证用户不的密码
String getPassword();
//返回认证用户不的用户名
String getUsername();
//账户是否未过期
boolean isAccountNonExpired();
//账号是否为解锁状态
boolean isAccountNonLocked();
//账号的凭证是否未过期
boolean isCredentialsNonExpired();
//账号是否启用
boolean isEnabled();
}
我们需要将数据库中查询到的用户信息,封装成一个UserDetails对象并交给Spring Security,因此我们还需要借助另一个类:UserDetailsService,该接口中仅有一个方法loadUserByUsername,专门用于基于用户名从数据库中查询用户信息。
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
自定义UserDetailsService
//service层创建一个UserDetailsService接口的实现类
@Service
public class UserServiceImpl implements UserDetailsService {
//这里要注入mapper层从数据库获取用户信息,这里模拟一下用户
private List<String> users= Arrays.asList("xiaowu","xiaoli");
//模拟分配的角色
private static HashMap<String, String[]> AUTHORITIES=new HashMap<>();
static {
AUTHORITIES.put("xiaowu",new String[]{"ROLE_USER","ROLE_ADMIN"});
AUTHORITIES.put("xiaoli",new String[]{"ROLE_USER"});
AUTHORITIES.put("xiaoming",new String[]{"ROLE_USER","ROLE_ADMIN"});
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//基于用户名查询用户对象,如果用户不存在抛出异常
if(!users.contains(username)){
throw new UsernameNotFoundException("用户名或密码错误!");
}
//如果查到了,返回的对象必须是UserDetails接口的实现类
//--可以通过Spring Security提供的User类来创建,将数据库查询到的用户名、密码、角色等封装进去
String password = "$2a$10$YvBKyAXzeuJTtApJqD4jCOWZYAznZcXhurF7321zKmPXBlYqJJcaC"; //123456加密后的密码
UserDetails details = User.withUsername(username)//用户名
.password(password) //密码
.authorities(AUTHORITIES.get(username))
.build();//权限信息
return details;
}
}
前提是在配置类中加入:
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
四、配置记住我功能
在自己定义的配置类中依赖注入刚刚创建的UserDetailsService并书写以下代码
//在配置类中依赖注入自定义的UserServiceImpl
@Autowired
private UserServiceImpl userService;
//在configure方法中添加
http.rememberMe() //开启记住我功能
.rememberMeParameter("remember-me") //默认记住我参数名,也就是提交表单时候的属性名
.tokenValiditySeconds(60*60*24) //默认记住我有效时间,单位秒,默认2周
.userDetailsService(userService); //在用户第一次访问时,会调用自定义的UserDetailsService的loadUserByUsername方法,根据用户名查询用户信息,并返回给框架,框架会做密码校验,如果密码正确,则登录成功,否则登录失败。
五、获取用户认证信息
登录成功后,有时候需要获取到认证后的用户信息,那么此时我们需要接触到两个新的对象
SecurityContextHolder和Authentication,Security中有SecurityContext,该对象封装了Security应用的上下文数据,并且该对象可以通过工具类。SecurityContextHolder访问到,因此让我们使用Security可以变得更加简单一点。
Authentication代表的是认证信息对象,当一个用户认证通过以后。就会封装成改接口的实现类,存入到SecurityContext中,该对象包含用户认证后的所有信息,因此我们在开发的时候经常使用。
public interface Authentication extends Principal, Serializable {
//当前用户拥有的权限列表,每一个权限对象都必须是GranteAuthority的实现类,不过大部分情况下都是只需要保存一个字符串。
Collection<? extends GrantedAuthority> getAuthorities();
//凭证信息,在用户名密码认证场景下,等同 于用户密码,在用户认证成功后为了保证安全性,这个值会被删除。
Object getCredentials();
//认证用户的详细信息,通常为webAuthenticationDetails接口的实现类,保存了用户的ip、sessionId等信息
Object getDetails();
//主体身份信息,再认证通过后通常都是userDetails接口的实现类对象,可以理解为UserDtailsService所返回的哪个对象
Object getPrincipal();
//是否已认证,只有返回true才表示当前用户是已经通过认证了的
boolean isAuthenticated();
//设置是否已认证属性
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
Spring Security给我们提供了很多种获取用户信息的方式,可以在Controller的方法中直接注入Authentication对象,甚至可以通过请求对象获取用户凭证对象,但是我们推荐的还是通过SecurityContextHolder 工具类来获取,对其进行再次封装工具类,可以直接使用。
public class SecurityUtils {
/**
* 获取用户
*/
public static UserDetails getLoginUser() {
Object principal = getAuthentication().getPrincipal();
//这里判断是为了防止不走认证就直接访问页面的情况,不然会报空指针异常
if(principal instanceof UserDetails){
return (UserDetails) principal;
}
return null;
}
/**
* 获取Authentication
* @return
*/
private static Authentication getAuthentication() {
return SecurityContextHolder.getContext().getAuthentication();
}
/**
*使用Bcrypt算法加密密码
*/
public static String encryptPassword(String password) {
return new BCryptPasswordEncoder().encode(password);
}
}
// http://127.0.0.1:8080/user
@RequestMapping("/user")
@ResponseBody
public User user() {
UserDetails user1 = SecurityUtils.getLoginUser();
System.out.println(user1.getUsername());
System.out.println(user1.getPassword());
Collection<? extends GrantedAuthority> authorities = user1.getAuthorities();
authorities.stream().map(GrantedAuthority::getAuthority).forEach(System.out::println);
}
打印如图所示:
六、用户的认证鉴权改进
回顾一下登录的逻辑
基于Spring Security认证流程如下图:
在之前案例演示中,只能用于前后端不分离的场景,而当我们开发前后端分离项目,需要后端返回的是数据而不是页面,这时候就会存在一定问题了,其实我们可以通过自己提供的登录接口,以及自定义登录失败处理器的的方案实现返回数据的方式。
标签:用户名,登录,Spring,用户,认证,Security,public From: https://blog.csdn.net/wuguanghui1/article/details/144468956