spring security工作流程

1.拦截登录,获取到用户的所有权限信息

1.1默认的拦截器拦截 /login 方法

  • OncePerRequestFilter 集成这个过滤器,每一次请求都将会被拦截过滤

  • 默认使用 UsernamePasswordAuthenticationFilter 来拦截用户的login操作。但是获取的用户名、密码默认是通过formdata传递过来的(类似于get方式,数据跟在链接后面)。但是对于json的请求参数就获取不到了,所以需要改写这个类

    • public class UsernamePasswordAuthenticationFilter extends
      		AbstractAuthenticationProcessingFilter {  
        public Authentication attemptAuthentication(HttpServletRequest request,
      			HttpServletResponse response) throws AuthenticationException {
      		if (postOnly && !request.getMethod().equals("POST")) {
      			throw new AuthenticationServiceException(
      					"Authentication method not supported: " + 				request.getMethod());
      		}
      		//从request中获取到用户名
      		String username = obtainUsername(request);
          	//获取密码
      		String password = obtainPassword(request);
      
      		if (username == null) {
      			username = "";
      		}
      		if (password == null) {
      			password = "";
      		}
      		username = username.trim();
      
      		UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
      				username, password);
      
      		// Allow subclasses to set the "details" property
      		setDetails(request, authRequest);
      
      		return this.getAuthenticationManager().authenticate(authRequest);
      	}
      
      
      }
      
      

1.2 自定义拦截方法

public class MyUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

拦截登录

/**
     * 父类的方法获取用户名,密码的方式是从request.getParameter中获得
     * @param request
     * @param response
     * @return
     * @throws AuthenticationException
     */
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
  //获取request的请求体
  String param = this.getRequestBody(request);
  String userName = null;
  String password = null;
  if (!StringUtils.isEmpty(param)) {
    JSONObject paraJson = JSONObject.fromObject(param);
    userName = paraJson.getString("username");
    password = paraJson.getString("password");
  }

  if (!StringUtils.isEmpty(userName) && !StringUtils.isEmpty(password)) {
    userName = userName.trim();

    UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
      userName, password);

    // Allow subclasses to set the "details" property
    setDetails(request, authRequest);
    List<AuthenticationProvider> providers = new ArrayList<>();
    //这里必须将注入的provider添加进来,不然会爆NOP
    providers.add(daoAuthenticationProvider);
    this.setAuthenticationManager(new ProviderManager(providers));
    //调用父类的验证方法
    return this.getAuthenticationManager().authenticate(authRequest);
  }
  return null;
}
/**
     * 获取请求体中的内容
     * @param request
     * @return
     */
private String getRequestBody(HttpServletRequest request) {
  BufferedReader br = null;
  StringBuilder sb = new StringBuilder("");
  try
  {
    br = request.getReader();
    String str;
    while ((str = br.readLine()) != null)
    {
      sb.append(str);
    }
    br.close();
  }
  catch (IOException e)
  {
    e.printStackTrace();
  }
  finally
  {
    if (null != br)
    {
      try
      {
        br.close();
      }
      catch (IOException e)
      {
        e.printStackTrace();
      }
    }
  }
  return sb.toString();

}

需要自定义注解起作用的话必须加上 http.addFilterAt(new MyUsernamePasswordAuthenticationFilter(daoAuthenticationProvider()), UsernamePasswordAuthenticationFilter.class); 代码,将自定义的拦截过滤器替换掉默认的。必须传一个AuthenticationProvider()过去,不然就会爆NOP

@Override
protected void configure(HttpSecurity http) throws Exception {
  http
    .authorizeRequests()
   
    .authorizeRequests().anyRequest().authenticated()
    //.and()
    //   .formLogin().loginPage("/login")
    //设置默认登录成功跳转页面
    //.defaultSuccessUrl("/index", true)
    //.successHandler(myAuthenticationSuccessHandler)
    //                    .permitAll()
    .and()
    .logout()
    .permitAll()
    .and()
    .csrf() //CSRF(Cross-site request forgery)跨站请求伪造
    .disable()
    ;
  //默认是调用 内置的UsernamePasswordAuthenticationFilter,而它接收参数只是从类似get方式传递的参数。而requestBody似乎就
  // 获取不到。这个方法是将自定义的过滤器来替换掉security默认的过滤器的作用
  http.addFilterAt(new MyUsernamePasswordAuthenticationFilter(daoAuthenticationProvider()), UsernamePasswordAuthenticationFilter.class);
}

1.3 拦截器的工作原理

经过一系列的过滤拦截跑到了自定义的过滤器方法中

通过调用各个authenticate方法最后代理到自定义的UserDetailService来校验用户是否能够登录成功和拿到用户的所有权限信息

调用自定义

AbstractUserDetailsAuthenticationProvider
try {
		preAuthenticationChecks.check(user);
		//校验密码是否正确之前注入的provider(DaoAuthenticationProvider)
		additionalAuthenticationChecks(user,
					(UsernamePasswordAuthenticationToken) authentication);
	}
	
@SuppressWarnings("deprecation")
protected void additionalAuthenticationChecks(UserDetails userDetails,
                                              UsernamePasswordAuthenticationToken authentication)
  throws AuthenticationException {
  if (authentication.getCredentials() == null) {
    logger.debug("Authentication failed: no credentials provided");

    throw new BadCredentialsException(messages.getMessage(
      "AbstractUserDetailsAuthenticationProvider.badCredentials",
      "Bad credentials"));
  }

  //请求登录时传递过来的密码
  String presentedPassword = authentication.getCredentials().toString();
  //判断请求传递过来的密码与数据库中查询出来的是否匹配
  // userDetails.getPassword()数据库中查询出来的并且已经通过某种指定的加密方式加密的密码
  //passwordEncoder.matches(明文,密文)通过某种解密方式解密密文看得到的明文是否与方法中的明文相匹配
  if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
    logger.debug("Authentication failed: password does not match stored value");

    throw new BadCredentialsException(messages.getMessage(
      "AbstractUserDetailsAuthenticationProvider.badCredentials",
      "Bad credentials"));
  }
}

  • 自定义的UserDetailService

    设置的密码信息必须是要跟注入的DaoAuthenticationProvider使用的加密方式一致,否则等登录的时候密码校验通不过

@Service
public class CustUserDetailService implements UserDetailsService {
    @Autowired
    private IUserRoleService userRoleService;
    @Autowired
    private IUserService userService;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        User user = userService.getUserByUserCode(s);
        Assert.isTrue(null != user, "用户不存在");
        //查询该用户的对应权限
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        List<UserRole> roleList = this.userRoleService.getRoleList(s);
        if (!CollectionUtils.isEmpty(roleList)) {
            roleList.forEach(userRole -> authorities.add(
                    new SimpleGrantedAuthority(userRole.getRoleCode())
            ));
        }

        AuthUser authUser = new AuthUser(s, user.getUserPwd(), authorities);
        authUser.setId(user.getId());
        authUser.setNickname(user.getUserName());
      	//设置的密码信息必须是要跟注入的DaoAuthenticationProvider使用的加密方式一致,否则等登录的时候密码校验通不过
        return new org.springframework.security.core.userdetails.User(user.getUserName(),
                new BCryptPasswordEncoder().encode(user.getUserPwd()), authorities);
    }
}  
  • 关键的bean注入
	//注入 DaoAuthenticationProvider 给providerManager
    @Bean
    public DaoAuthenticationProvider daoAuthenticationProvider() {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        //设置对应的密码加密方式。所以要在对应的UserDetailService上面使用这种加密方式给密码加密,否则最后会登录不上
        daoAuthenticationProvider.setPasswordEncoder(new BCryptPasswordEncoder());
        daoAuthenticationProvider.setUserDetailsService(custUserDetailService());
        return daoAuthenticationProvider;
    }

    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    @Autowired//注意这个方法是注入的
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(custUserDetailService());
    }
@EnableWebSecurity
//启用@preAuth注解,角色编码必须以 ROLE_开头
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{

    @Bean
    public CustUserDetailService custUserDetailService(){
        return new CustUserDetailService();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {

        authenticationManagerBuilder.userDetailsService(custUserDetailService());
    }

    /**
     *   1.首先当我们要自定义Spring Security的时候我们需要继承自WebSecurityConfigurerAdapter来完成,相关配置重写对应 方法即可。
         2.我们在这里注册CustomUserService的Bean,然后通过重写configure方法添加我们自定义的认证方式。
         3.在configure(HttpSecurity http)方法中,我们设置了登录页面,而且登录页面任何人都可以访问,然后设置了登录失败地址,也设置了注销请求,注销请求也是任何人都可以访问的。
         4.permitAll表示该请求任何人都可以访问,.anyRequest().authenticated(),表示其他的请求都必须要有权限认证。
         5.这里我们可以通过匹配器来匹配路径,比如antMatchers方法,假设我要管理员才可以访问admin文件夹下的内容,我可以这样来写:.antMatchers("/admin/**").hasRole("ROLE_ADMIN"),也可以设置admin文件夹下的文件可以有多个角色来访问,写法如下:.antMatchers("/admin/**").hasAnyRole("ROLE_ADMIN","ROLE_USER")
         6.可以通过hasIpAddress来指定某一个ip可以访问该资源,假设只允许访问ip为210.210.210.210的请求获取admin下的资源,写法如下.antMatchers("/admin/**").hasIpAddress("210.210.210.210")
         7.更多的权限控制方式参看下表:
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                    .authorizeRequests().anyRequest().authenticated()
              
                .and()
                    .logout()
                    .permitAll()
                .and()
                    .csrf() //CSRF(Cross-site request forgery)跨站请求伪造
                        .disable()
                ;
        //默认是调用 内置的UsernamePasswordAuthenticationFilter,而它接收参数只是从类似get方式传递的参数。而requestBody似乎就
        // 获取不到。这个方法是将自定义的过滤器来替换掉security默认的过滤器的作用
          http.addFilterAt(new MyUsernamePasswordAuthenticationFilter(daoAuthenticationProvider()), UsernamePasswordAuthenticationFilter.class);
    }
}
  • 限制方法访问权限

    • 添加注解

    • **@EnableWebSecurity **

    • @EnableGlobalMethodSecurity(prePostEnabled = true)

    • /*
      which means that access will only be allowed for users with the role "ROLE_USER". Obviously the same thing could easily be achieved using a traditional configuration and a simple configuration attribute for the required role. But what about:
      */
      //如上,需要定义的角色编码为 ROLE_ 开头
      @PreAuthorize("hasRole('USER')")
      public void create(Contact contact);
      
      @GetMapping("/index")
      //说明拥有角色 ROLE_admin角色
      @PreAuthorize("hasRole('admin')")
      public Map<String, Object> index() {
        Map<String, Object> map = new HashMap<>(2);
      
        map.put("success", true);
        map.put("time", System.currentTimeMillis());
        return map;
      }
      

1.4拦截登录流程

登录代码

String userCode = json.getString("username");
String pwd = json.getString("password");
UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken(userCode, pwd);
//authenticationManager 注入的是 ProviderManager
final Authentication authentication = authenticationManager.authenticate(upToken);
SecurityContextHolder.getContext().setAuthentication(authentication);

// AbstractUserDetailsAuthenticationProvider

public Authentication authenticate(Authentication authentication)
      throws AuthenticationException {
   Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
         messages.getMessage(
               "AbstractUserDetailsAuthenticationProvider.onlySupports",
               "Only UsernamePasswordAuthenticationToken is supported"));

   // Determine username
   String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
         : authentication.getName();

   boolean cacheWasUsed = true;
   UserDetails user = this.userCache.getUserFromCache(username);

   if (user == null) {
      cacheWasUsed = false;

      try {
        // retrieveUser调用注入的具体的UserDetailService,获取用户权限信息
         user = retrieveUser(username,
               (UsernamePasswordAuthenticationToken) authentication);
      }
      catch (UsernameNotFoundException notFound) {
         logger.debug("User '" + username + "' not found");

         if (hideUserNotFoundExceptions) {
            throw new BadCredentialsException(messages.getMessage(
                  "AbstractUserDetailsAuthenticationProvider.badCredentials",
                  "Bad credentials"));
         }
         else {
            throw notFound;
         }
      }

      Assert.notNull(user,
            "retrieveUser returned null - a violation of the interface contract");
   }

   try {
      preAuthenticationChecks.check(user);
      additionalAuthenticationChecks(user,
            (UsernamePasswordAuthenticationToken) authentication);
   }
   catch (AuthenticationException exception) {
      if (cacheWasUsed) {
         // There was a problem, so try again after checking
         // we're using latest data (i.e. not from the cache)
         cacheWasUsed = false;
         user = retrieveUser(username,
               (UsernamePasswordAuthenticationToken) authentication);
         preAuthenticationChecks.check(user);
         additionalAuthenticationChecks(user,
               (UsernamePasswordAuthenticationToken) authentication);
      }
      else {
         throw exception;
      }
   }

   postAuthenticationChecks.check(user);

   if (!cacheWasUsed) {
      this.userCache.putUserInCache(user);
   }

   Object principalToReturn = user;

   if (forcePrincipalAsString) {
      principalToReturn = user.getUsername();
   }

   return createSuccessAuthentication(principalToReturn, authentication, user);
}

调用对应的UserDetailService的loadUserByUsername 获取权限信息

protected final UserDetails retrieveUser(String username,
      UsernamePasswordAuthenticationToken authentication)
      throws AuthenticationException {
   prepareTimingAttackProtection();
   try {
      UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
      if (loadedUser == null) {
         throw new InternalAuthenticationServiceException(
               "UserDetailsService returned null, which is an interface contract violation");
      }
      return loadedUser;
   }
   catch (UsernameNotFoundException ex) {
      mitigateAgainstTimingAttack(authentication);
      throw ex;
   }
   catch (InternalAuthenticationServiceException ex) {
      throw ex;
   }
   catch (Exception ex) {
      throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
   }
}

获取到权限信息

//user: 从数据库查出的用户信息
// authentication :登录传过来组装的用户信息
// 判断密码是否正确
additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
AbstractUserDetailsAuthenticationProvider.authenticate

校验密码是否正确

转载于:https://my.oschina.net/u/3711426/blog/1865145

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值