OncePerRequestFilter 集成这个过滤器,每一次请求都将会被拦截过滤java
默认使用 UsernamePasswordAuthenticationFilter 来拦截用户的login操做。可是获取的用户名、密码默认是经过formdata传递过来的(相似于get方式,数据跟在连接后面)。可是对于json的请求参数就获取不到了,因此须要改写这个类spring
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); } }
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()过去,否则就会爆NOPjson
@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); }
通过一系列的过滤拦截跑到了自定义的过滤器方法中app
经过调用各个authenticate方法最后代理到自定义的UserDetailService来校验用户是否可以登陆成功和拿到用户的全部权限信息ide
调用自定义post
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")); } }
自定义的UserDetailServiceui
设置的密码信息必须是要跟注入的DaoAuthenticationProvider使用的加密方式一致,不然等登陆的时候密码校验通不过this
@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); } }
//注入 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; }
登陆代码
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
校验密码是否正确