Spring Security核心组件

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;
}
  1. Authentication是spring security包中的接口,直接继承自Principal类,而Principal是位于 java.security 包中的。它是表示着一个抽象主体身份,任何主体都有一个名称,因此包含一个getName()方法。
  2. getAuthorities(),权限信息列表,默认是GrantedAuthority接口的一些实现类,通常是代表权限信息的一系列字符串。
  3. getCredentials(),凭证信息,用户输入的密码字符串,在认证过后通常会被移除,用于保障安全。
  4. getDetails(),细节信息,web应用中的实现接口通常为 WebAuthenticationDetails,它记录了访问者的ip地址和sessionId的值。
  5. 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/

发表评论

电子邮件地址不会被公开。