【学习笔记】SpringSecurity入门篇-进阶篇(库存)

326 阅读3分钟

spring security

引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

底层原理

过滤链

FilterSecurityInterceptor:方法级别的权限过滤器,基本位于最底层

ExceptionTranslationFilter:异常过滤器

UsernamePasswordAuthenticationFilter:校验用户名密码过滤器

过滤器加载

.....

两大接口

用于自定义开发

UsewrDetailsService接口:

查询数据库用户名和密码过程

继承UsernamePasswordAuthenticationFilter,重写里面的attempAuthenticationFilter方法进行验证,然后重写该接口父类中的successAuthentication方法和unsuccessAuthentication方法。

创建件一个类实现userDetailsService接口,编写查询数据库过程,返回一个User对象。这个User对象是安全框架提供的对象。

passwordEncoder接口: 实现类BCryptPasswordEncoder()

数据加密接口,用于返回user对象那个里面密码加密

实践

1.设置登录的用户名和密码

  • 配置文件

    spring:
      security:
        user:
          name: xxxx
          password: xxxxx
    
  • 配置类

    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
            String password = passwordEncoder.encode("123");
            auth.inMemoryAuthentication().withUser("tom").password(password).roles("admin");
    
        }
        @Bean
        PasswordEncoder password() {
            return new BCryptPasswordEncoder();
        }
    }
    
  • 自定义编写实现类

    • 第一步 创建配置类,设置使用哪个userdetailsService实现类

      @Configuration
      public class MySecurityConfig extends WebSecurityConfigurerAdapter {
      
          @Autowired
          private UserDetailsService userDetailsService;
          @Override
          protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(password());
      }
      
          @Bean
          PasswordEncoder password() {
              return new BCryptPasswordEncoder();
          }
      }
      
    • 第二步 编写实现类,返回user对象,user对象有用户名密码操作

      @Service("userDetailsService")
      public class MyUserDetailService implements UserDetailsService {
      
          @Override
          public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
              List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
              return new User("li", new BCryptPasswordEncoder().encode("123"), auths);
          }
      }
      
测试
@Service("userDetailsService")
public class MyUserDetailService implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        QueryWrapper<com.zero.entity.User> wrapper = new QueryWrapper<>();
        wrapper.eq("username", username);
        com.zero.entity.User user = userMapper.selectOne(wrapper);
        if(user == null) {
            throw new UsernameNotFoundException("用户名找不到");
        }

        List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
        return new User(user.getUsername(), new BCryptPasswordEncoder().encode(user.getPassword()), auths);
    }
}

配置自定义认证页面

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.formLogin() //自定义编写登录页面
    .loginPage("/login.html") //登录页面
    .loginProcessingUrl("/user/login") //自定义访问路径
    .defaultSuccessUrl("/test/index").permitAll() //登录成功后跳转路径
    .and().authorizeRequests()
            .antMatchers("/","/test/hello","/user/login").permitAll() //哪些路径不需要认证,可以直接访问
    .anyRequest().authenticated()
            .and().csrf().disable(); //关闭csrf防护
}

授权

hasAuthority方法:当前主体有指定的权限

1.配置类设置当前访问路径有哪些权限

.antMatchers("test/index").hasAuthority("admins")//拥有admins权限才可以访问

hasAnyAuthority方法:拥有多个权限

.antMatchers("/test/index").hasAnyAuthority("admins,manager") //拥有多个权限中的一个就可以访问

hasRole方法:拥有角色

hasAnyRole方法:拥有角色中的一个

自定义无权限页面

http.exceptionHandling().accessDeniedPage("/unauth.html");//设置无权限页面

认证相关注解

@secured:用户具有某个角色,可以访问

  • 使用@EnableGlobalMethodSecurity(securedEnabled=true)开启注解

  • 在controller方法上使用注解,设置角色

  • @GetMapping("/update")
    @Secured({"ROLE_sale","ROLE_manager"}) //只有拥有这两种角色才能访问这个方法
    public String update() {
        return "update";
    }
    

@PreAuthorize:方法进入前进行权限验证

  • 启动类上开启
  • EnableGlobalMethodSecurity(prepostenabled=true)开启
@PreAuthorize("hasAnyAuthority('admins')")
public String update() {

    return "update";
}

@postAuthorize:方法完成后进行验证,一般验证返回值

  • 启动类上开启

  • EnableGlobalMethodSecurity(prepostenabled=true)开启

@PreAuthorize("hasAnyAuthority('admins')")
public String update() {

    return "update";
}

postfilter:返回的数据过滤

prefilter:传入方法数据过滤

@PostFilter("filterObject.username=='wang'")
public List<User> update() {
    System.out.println("test");
    List<User> list = new ArrayList<>();
    list.add(new User(1,"wang","123"));
    list.add(new User(12,"admin2","1234"));
    return list;
}

用户注销

//退出
http.logout().logoutUrl("/logout").logoutSuccessUrl("/test/hello").permitAll();

认证流程原理:

usernamepasswordAuthennticationFilter【继承的是AbstractAuthenticationProcessingFilter ,如果自己要自定义认证filter,继承这个即可】,将表单用户信息封装为authentication(其实现类为usernamepasswordauthenticationtoken)然后交给authenticationmanager认证authentication,委托给absauthentication进行验证(在daoauthentication获取userdatail信息,然后校验密码填充authentication),返回authtication,通过securirycontextholder.getcontext(),setauthentication()将authentication保存至上下文。

image-20210428120035277

image-20210428120244153

image-20210428120359899

授权流程(web授权,方法授权)

1.拦截请求。已经认证的用户访问受保护的资源将被securityFilterChain中的FilterSecurityInterceptor的子类拦截。

2.获取资源管理访问策略,FilterSecurityInterceptor会从SecurityMetadataSource子类DefaultFilterSecurityMetadataSource获取要访问当前资源所需要的权限Collection.

3.最后,FilterSecurityInterceptor会调用AccessDecisionManager进行授权决策,决定是否允许访问。

自动登录

@Autowired
private DataSource dataSource;
@Bean
public PersistentTokenRepository persistentTokenRepository() {
    JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
    jdbcTokenRepository.setDataSource(dataSource);
    jdbcTokenRepository.setCreateTableOnStartup(true);
    return jdbcTokenRepository;
}
./bin/mysqld --initialize --user=mysql --basedir=/usr/local/mysql --\
datadir=/usr/local/mysql/data

原理:

认证

在usernamePasswordAuthenticationFilter通过后,调用remebermeservices,进行业务逻辑操作,将token写入浏览器同时也要保存到数据库中,下次访问的时候,通过 remembermeauthenticationfilter进行过滤,然后调用autologin。就会将浏览器中带的token和数据中的token进行比对,实现自动登录

protected void successfulAuthentication(HttpServletRequest request,
			HttpServletResponse response, FilterChain chain, Authentication authResult)
			throws IOException, ServletException {

		if (logger.isDebugEnabled()) {
			logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
					+ authResult);
		}

		SecurityContextHolder.getContext().setAuthentication(authResult);

		rememberMeServices.loginSuccess(request, response, authResult);

		// Fire event
		if (this.eventPublisher != null) {
			eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
					authResult, this.getClass()));
		}

		successHandler.onAuthenticationSuccess(request, response, authResult);
	}
protected void onLoginSuccess(HttpServletRequest request,
      HttpServletResponse response, Authentication successfulAuthentication) {
   String username = successfulAuthentication.getName();

   logger.debug("Creating new persistent login for user " + username);

   PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(
         username, generateSeriesData(), generateTokenData(), new Date());
   try {
      tokenRepository.createNewToken(persistentToken);
      addCookie(persistentToken, request, response);
   }
   catch (Exception e) {
      logger.error("Failed to save persistent token ", e);
   }
}

进阶篇: juejin.cn/post/698991…