Authentication
认证信息,在Spring Security用户认证流程中,用户提交的信息被Spring Security封装成一个Authentication对象,不同的认证方式需要的Authentication实现可能是不同的,通常表单登录使用的是实现类UsernamePasswordAuthenticationToken
,它包含用户的用户名、密码等信息。
public interface Authentication extends Principal, Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
Object getCredentials();
Object getDetails();
Object getPrincipal();
boolean isAuthenticated();
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
- Authentication是spring security包中的接口,直接继承自
Principal
类,而Principal是位于java.security
包中的。它是表示着一个抽象主体身份,任何主体都有一个名称,因此包含一个getName()
方法。 getAuthorities()
,权限信息列表,默认是GrantedAuthority
接口的一些实现类,通常是代表权限信息的一系列字符串。getCredentials()
,凭证信息,用户输入的密码字符串,在认证过后通常会被移除,用于保障安全。getDetails()
,细节信息,web应用中的实现接口通常为 WebAuthenticationDetails,它记录了访问者的ip地址和sessionId的值。getPrincipal()
,身份信息,大部分情况下返回的是UserDetails接口的实现类,UserDetails代表用户的详细信息,那从Authentication中取出来的UserDetails就是当前登录用户信息,它也是框架中的常用接口之一。
UserDetailService
UserDetailService是Spring Security中较为核心的一个组件,在认证流程中,Spring Security通过它根据用户名获取用户密码,然后进行密码比对。
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
可以通过User类的链式build来获取一个UserDetails的实例对象。
UserDetails userDetails = User.withUsername("zhangsan").password("123").authorities("p1").build()
Spring Security提供的InMemoryUserDetailsManager
(内存认证),JdbcUserDetailsManager
(jdbc认证)就是UserDetailsService
的实现类,主要区别无非就是从内存还是从数据库加载用户。我们可以自定义一个UserDetailsService
并注入来实现自定义用户数据来源的功能。
例:使用数据库加载用户信息:
@Data
public class UserDto {
private Long id;
private String username;
private String password;
private String fullname;
private String mobile;
}
@Repository
public class UserDao {
JdbcTemplate jdbcTemplate;
public UserDto getUserByUsername(String username){
String sql = "select id,username,password,fullname,mobile from t_user where username=?";
List<UserDto> list = jdbcTemplate.query(sql,new Object[]{username},new BeanPropertyRowMapper<>(UserDto.class));
if(list.size()<=0){
return null;
}
return list.get(0);
}
}
@Component
public class SpringDataUserDetailsService implements UserDetailsService {
@Autowired
UserDao userDao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserDto userDto = userDao.getUserByUsername(username);
if(userDto == null) return null;
return User.withUsername(userDto.getUsername()).password(userDto.getPassword()).authorities("p1").build();
}
}
PasswordEncoder
DaoAuthenticationProvider认证处理器通过UserDetailsService获取到UserDetails后,通过PasswordEncoder与请求Authentication中的密码进行比对,因为数据库中获取的密码通常是加密过的。
常用的是BCrypt算法,如果使用BCryptPasswordEncoder,在添加进数据库之前也得使用BCrypt加密。
自定义认证
如果springboot需要使用jsp,需要引入
<!-- spring boot 内置tomcat jsp支持 -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<!--jsp页面使用jstl标签-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
配置资源目录:
<build>
<resources>
<resource>
<directory>src/main/webapp</directory>
<targetPath>META-INF/resources</targetPath>
<includes>
<include>**/*.*</include>
</includes>
</resource>
</resources>
</build>
将login.jsp放在webapp/WEB-INF/view下,配置视图解析器:
spring.mvc.view.prefix= /WEB-INF/view/
spring.mvc.view.suffix= .jsp
配置路径映射:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("redirect:/login-view");
registry.addViewController("/login-view").setViewName("login");
}
}
配置SpringSecurity的登录页,和处理路径:
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//定义用户信息服务(查询用户信息)
@Bean
public UserDetailsService userDetailsService(){
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build());
return manager;
}
//密码编码器
@Bean
public PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
//安全拦截机制
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/r/r1").hasAuthority("p1")
.antMatchers("/r/r2").hasAuthority("p2")
.antMatchers("/r/**").authenticated()//所有"/r/**"请求必须认证通过
.anyRequest().permitAll()//除了"/r/**"所有请求可以访问
.and()
.formLogin()//允许表单登录
.loginPage("/login-view")//配置登录页
.loginProcessingUrl("/login")//配置处理路径
.successForwardUrl("/login-success");//自定义登陆成功的页面地址
}
}
为了防止spring security的csrf拦截机制,可以将其关闭:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/r/r1").hasAuthority("p1")
.antMatchers("/r/r2").hasAuthority("p2")
.antMatchers("/r/**").authenticated()//所有"/r/**"请求必须认证通过
.anyRequest().permitAll()//除了"/r/**"所有请求可以访问
.and()
.formLogin()//允许表单登录
.loginPage("/login-view")
.loginProcessingUrl("/login")
.successForwardUrl("/login-success");//自定义登陆成功的页面地址
}
或者在login.jsp页面添加一个token:
<form action="login" method="post">
<input type="hidden" name="{_csrf.parameterName}" value="{_csrf.token}"/>
用户:<input type="text" name="username"><br/>
密码:<input type="password" name="password"><br/>
<input type="submit" value="登录"/>
</form>
原创文章,作者:彭晨涛,如若转载,请注明出处:https://www.codetool.top/article/spring-security%e6%a0%b8%e5%bf%83%e7%bb%84%e4%bb%b6/