Spring Security的HTTP权限控制web
一个可以为基于Spring的企业应用系统提供声明式的安全訪问控制解决方式的安全框架(简单说是对访问权限进行控制嘛),应用的安全性包括用户认证(Authentication)和用户受权(Authorization)两个部分。用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户可否访问该系统。用户认证通常要求用户提供用户名和密码。系统经过校验用户名和密码来完成认证过程。用户受权指的是验证某个用户是否有权限执行某个操做。在一个系统中,不一样用户所具备的权限是不一样的。好比对一个文件来讲,有的用户只能进行读取,而有的用户能够进行修改。通常来讲,系统会为不一样的用户分配不一样的角色,而每一个角色则对应一系列的权限。 spring security的主要核心功能为 认证和受权,全部的架构也是基于这两个核心功能去实现的。
springSecurity在咱们进行用户认证以及授予权限的时候,经过各类各样的拦截器来控制权限的访问,从而实现安全。spring
在springboot中整合springSecurityjson
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
自定义了一个springSecurity安全框架的配置类 继承WebSecurityConfigurerAdapter,重写其中的方法configure,在web容器启动的过程当中该类实例对象会被WebSecurityConfiguration类处理。api
import com.wqy.springbootes.security.AuthFilter; import com.wqy.springbootes.security.AuthProvider; import com.wqy.springbootes.security.LoginAuthFailHandler; import com.wqy.springbootes.security.LoginUrlEntryPoint; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; /** * Created by wqy. * * 密码:admin/admin */ @EnableWebSecurity @EnableGlobalMethodSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { /** * HTTP权限控制 * @param http * @throws Exception */ @Override protected void configure(HttpSecurity http) throws Exception { http.addFilterBefore(authFilter(), UsernamePasswordAuthenticationFilter.class); // 资源访问权限 http.authorizeRequests() .antMatchers("/admin/login").permitAll() // 管理员登陆入口 .antMatchers("/static/**").permitAll() // 静态资源 .antMatchers("/user/login").permitAll() // 用户登陆入口 .antMatchers("/admin/**").hasRole("ADMIN") .antMatchers("/user/**").hasAnyRole("ADMIN", "USER") .antMatchers("/api/user/**").hasAnyRole("ADMIN", "USER") .and() .formLogin() .loginProcessingUrl("/login") // 配置角色登陆处理入口 .failureHandler(authFailHandler()) .and() .logout() .logoutUrl("/logout") .logoutSuccessUrl("/logout/page") .deleteCookies("JSESSIONID") .invalidateHttpSession(true) .and() .exceptionHandling() .authenticationEntryPoint(urlEntryPoint())// AuthenticationEntryPoint 用来解决匿名用户访问无权限资源时的异常 .accessDeniedPage("/403"); http.csrf().disable(); // 关闭默认打开的crsf protection http.headers().frameOptions().sameOrigin();// 支持SAMEORIGIN的设置方式 } /** * 自定义认证策略 */ @Autowired public void configGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(authProvider()).eraseCredentials(true); }
@Bean public AuthProvider authProvider() { return new AuthProvider(); } @Bean public LoginUrlEntryPoint urlEntryPoint() { return new LoginUrlEntryPoint("/user/login"); } @Bean public LoginAuthFailHandler authFailHandler() { return new LoginAuthFailHandler(urlEntryPoint()); } @Bean public AuthenticationManager authenticationManager() { AuthenticationManager authenticationManager = null; try { authenticationManager = super.authenticationManager(); } catch (Exception e) { e.printStackTrace(); } return authenticationManager; } @Bean public AuthFilter authFilter() { AuthFilter authFilter = new AuthFilter(); authFilter.setAuthenticationManager(authenticationManager()); authFilter.setAuthenticationFailureHandler(authFailHandler()); return authFilter; } }
解析:能够自定义哪些URL须要权限验证,哪些不须要。只须要在咱们的SecurityConfig类中覆写configure(HttpSecurity http)方法便可。安全
方法解释:springboot
http.authorizeRequests()方法有不少子方法,每一个子匹配器将会按照声明的顺序起做用
指定用户能够访问的多个url模式。特别的,任何用户能够访问以"/static"开头的url资源
任何以"/admin"开头的请求限制用户具备 "ADMIN"角色。
任何以"/user"、“/api/user/”开头的请求限制用户具备 "ADMIN"或“USER”角色。*架构
源码解析:app
默认的configure(HttpSecurity http)方法继续向httpSecurity类中追加SecurityConfigurer的具体实现类,如authorizeRequests()方法追加一个ExpressionUrlAuthorizationConfigurer,formLogin()方法追加一个FormLoginConfigurer。框架
其中ExpressionUrlAuthorizationConfigurer这个实现类比较重要,由于他会给咱们建立一个很是重要的对象FilterSecurityInterceptor对象,FormLoginConfigurer对象比较简单,可是也会为咱们提供一个在安全认证过程当中常常用到会用的一个Filter:UsernamePasswordAuthenticationFilter。ide
添加受权过滤器
public class AuthFilter extends UsernamePasswordAuthenticationFilter {
@Autowired private IUserService userService; @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { String name = obtainUsername(request); if(!Strings.isNullOrEmpty(name)){ request.setAttribute("username",name); return super.attemptAuthentication(request, response); } User user = userService.findUserByUsername(name); if(Objects.equals(user.getUsername,name)){ return new UsernamePasswordAuthenticationToken(user,null,user.getAuthorities()); }else{ throw new BadCredentialsException("userCodeError"); } } }
解释:从request中取出authentication, authentication是在org.springframework.security.web.context.SecurityContextPersistenceFilter过滤器中经过捕获用户提交的登陆表单中的内容生成的一个org.springframework.security.core.Authentication接口实例.
public class LoginUrlEntryPoint extends LoginUrlAuthenticationEntryPoint {
private static final String API_FREFIX = "/api"; private static final String API_CODE_403 = "{\"code\": 403}"; private static final String CONTENT_TYPE = "application/json;charset=UTF-8"; private PathMatcher pathMatcher = new AntPathMatcher(); private final Map<String, String> authEntryPointMap; public LoginUrlEntryPoint(String loginFormUrl) { super(loginFormUrl); authEntryPointMap = new HashMap<>(); // 普通用户登陆入口映射 authEntryPointMap.put("/user/**", "/user/login"); // 管理员登陆入口映射 authEntryPointMap.put("/admin/**", "/admin/login"); } /** * 根据请求跳转到指定的页面,父类是默认使用loginFormUrl * @param request * @param response * @param exception * @return */ @Override protected String determineUrlToUseForThisRequest(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) { String uri = request.getRequestURI().replace(request.getContextPath(), ""); for (Map.Entry<String, String> authEntry : this.authEntryPointMap.entrySet()) { if (this.pathMatcher.match(authEntry.getKey(), uri)) { return authEntry.getValue(); } } return super.determineUrlToUseForThisRequest(request, response, exception); } /** * 若是是Api接口 返回json数据 不然按照通常流程处理 * @param request * @param response * @param authException * @throws IOException * @throws ServletException */ @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { String uri = request.getRequestURI(); if (uri.startsWith(API_FREFIX)) { response.setStatus(HttpServletResponse.SC_FORBIDDEN); response.setContentType(CONTENT_TYPE); PrintWriter pw = response.getWriter(); pw.write(API_CODE_403); pw.close(); } else { super.commence(request, response, authException); } }
public class LoginAuthFailHandler extends SimpleUrlAuthenticationFailureHandler {
private final LoginUrlEntryPoint urlEntryPoint; public LoginAuthFailHandler(LoginUrlEntryPoint urlEntryPoint) { this.urlEntryPoint = urlEntryPoint; } @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { String targetUrl = this.urlEntryPoint.determineUrlToUseForThisRequest(request, response, exception); targetUrl += "?" + exception.getMessage(); super.setDefaultFailureUrl(targetUrl); super.onAuthenticationFailure(request, response, exception); }
public class AuthProvider implements AuthenticationProvider { @Autowired private IUserService userService; private final Md5PasswordEncoder passwordEncoder = new Md5PasswordEncoder(); @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { String userName = authentication.getName(); String inputPassword = (String) authentication.getCredentials(); User user = userService.findUserByName(userName); if (user == null) { throw new AuthenticationCredentialsNotFoundException("authError"); } if (this.passwordEncoder.isPasswordValid(user.getPassword(), inputPassword, user.getId())) { return new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities()); } throw new BadCredentialsException("authError"); } @Override public boolean supports(Class<?> authentication) { return true; } }
解析:
拿到authentication对象后,过滤器会调用ProviderManager类的authenticate方法,并传入该对象.
ProviderManager类的authenticate方法再调用自身的doAuthentication方法,在doAuthentication方法中会调用类中的List<AuthenticationProvider> providers集合中的各个AuthenticationProvider接口实现类中的authenticate(Authentication authentication)方法进行验证
因而可知,真正的验证逻辑是由各个各个AuthenticationProvider接口实现类来完成的,DaoAuthenticationProvider类是默认状况下注入的一个AuthenticationProvider接口实现类.