SpringSecurity学习于实践

SpringSecurity的核心功能

1.认证(你是谁) 2.授权(能干嘛) 3.攻击防护(防止伪造身份)

SpringSecurity的原理

其实Spring Security是一系列的过滤器链

在这条顾虑其链上,如果我们定义的是表单认证,那么UsernamePasswordAuthenticationFilter就会起作用,如果是Basic认证,那么BasicAuthenticationFilter就起作用,这条链上如果不相关的是不会起作用的。最后到达FilterSecurityInterceptor来判断是否可以访问REST API,如果不符合就抛出异常,此时ExceptionTranslationFilter起作用。

自定义用户认证

处理用户信息获取逻辑

用户的信息被封装在了org.springframework.security.core.userdetails.UserDetailsService, 如果需要从自己的数据源获取用户数据,那么就需要实现UserDetailsService类,并实现loadUserByUsername(String s)

@Component//TODO 使之成为用户的bean
public class MyDetailService implements UserDetailsService {
	Logger logger = LoggerFactory.getLogger(MyDetailService.class);
	[@Override](https://my.oschina.net/u/1162528)
	public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
		//TODO 根据用户名查找用户信息
		logger.info("登陆用户名:"+s);
		return new User(s,"123456", AuthorityUtils.createAuthorityList("admin"));
}
}

处理用户校验逻辑

可以不使用SpringSecurity的User,可以自定义一个类实现UserDetail,实现它的下面的四个方法。 //账户是否过期 boolean isAccountNonExpired(); //账户是否被锁 boolean isAccountNonLocked(); //证书是否过期 boolean isCredentialsNonExpired(); //是否可用 boolean isEnabled();

例子:

@Component//TODO 使之成为用户的bean
public class MyDetailService implements UserDetailsService {
	Logger logger = LoggerFactory.getLogger(MyDetailService.class);
	[@Override](https://my.oschina.net/u/1162528)
	public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
		//TODO 根据用户名查找用户信息
		logger.info("登陆用户名:"+s);
		return new User(s,"123456",true,true,true,false, AuthorityUtils.createAuthorityList("admin"));
	}
}

处理密码加密解密

需要配置一个加密器:

@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
	//生成一个加密器
	@Bean
	public PasswordEncoder getPasswordEncoder(){
		return new BCryptPasswordEncoder();
	}
....}

@Component//TODO 使之成为用户的bean
public class MyDetailService implements UserDetailsService {
**		@Autowired
	PasswordEncoder passwordEncoder;**

	Logger logger = LoggerFactory.getLogger(MyDetailService.class);
	@Override
	public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
		//TODO 根据用户名查找用户信息
		logger.info("登陆用户名:"+s);
		String encode = passwordEncoder.encode("123456");
		logger.info("登陆用户名:"+encode);
		return new User(s,encode,true,true,true,true, AuthorityUtils.createAuthorityList("admin"));
	}
}

执行结果:

2018-11-28 21:23:10.046  INFO 7560 --- [nio-8088-exec-3] com.flexible.service.MyDetailService     : 登陆用户名:user
2018-11-28 21:23:10.133  INFO 7560 --- [nio-8088-exec-3] com.flexible.service.MyDetailService     : 登陆用户名:$2a$10$S7UuQCqoIEnfzJJGZOnJuuavzZFlohoXA4IpGDkmxmmV9H.01XUIS

自定义认证流程

自定义的登录界面

http.formLogin().loginPage("/imooc-signIn.html")//这里可以写登录的界面的url,但是在之前的配置已经默认的拦截所以的url,所以会出现循环的打开登录的界面,导致死循环,为了解决这种情况就需要使用.antMatchers("/imooc-signIn.html").permitAll()//表示匹配到登录的时候授权。此时在点击任何该服务的链接就会跳到登录界面,例如:

直接引导登录界面

  http.formLogin()
            .loginPage("/imooc-signIn.html")
            .and()
            .authorizeRequests()//对后面的请求授权
            .antMatchers("/imooc-signIn.html").permitAll()//表示匹配到登录的时候授权。
            .anyRequest()//任何请求
            .authenticated();//都需要身份认证

实现自定义登录界面

1.需要请求跳转。

2.存储跳转前的请求到httpSessionRequest里面。

3.将请求从缓存取出。

4.判断请求跳转时页面跳转还是其他的,如果页面就可以时直接跳转(但是要自定义跳转的登录页面就需要需要实现登录页面时可以定制化的)。

5.鉴定权限,如果不是就登录页面的请求跳转就是权限不够提示401。

例子: SecurityProperties.java

@ConfigurationProperties(prefix = "flexible.security")
public class SecurityProperties {
	private BrowserProperties browser = new BrowserProperties();

	public BrowserProperties getBrowser() {
		return browser;
	}

	public void setBrowser(BrowserProperties browser) {
		this.browser = browser;
	}
}

BrowserProperties.java

public class BrowserProperties {
	//配置默认的登录页
	private String loginPage="/imooc-signIn.html";

	public BrowserProperties() {
	}

	public String getLoginPage() {
		return loginPage;
	}

	public void setLoginPage(String loginPage) {
		this.loginPage = loginPage;
	}
}

SecurityCoreConfig.java(让配置生)

/**
 * 让我们配置的security配饰生效
 */
@Configuration
@EnableConfigurationProperties(SecurityProperties.class)
public class SecurityCoreConfig {
}

BrowserSecurityConfig.java(配置放行路径跳转到自己的路径)

		http.formLogin()
				.loginPage("/authentication/require")//自定义的认证路径
				.loginProcessingUrl("/authentication/form")//告诉springsecurity使用UsernamePasswordAuthenticationFilter来处理登录
				.and()
				.authorizeRequests()//对后面的请求授权,将自定义的登录页面页放权
				.antMatchers("/authentication/require",securityProperties.getBrowser().getLoginPage()).permitAll()//表示匹配到登录的时候授权。
				.anyRequest()//任何请求
				.authenticated()//都需要身份认证
				.and().csrf().disable();//将spring security为了防止CSRF攻击禁用

BrowserSecurityController.java

@RestController
public class BrowserSecurityController {
	Logger logger = LoggerFactory.getLogger(BrowserSecurityController.class);
	@Autowired
	SecurityProperties securityProperties;//获取到自定义的登录页
	//拿到引发跳转的请求需要通过HttpSessionRequestCache来拿
	private RequestCache requestCache = new HttpSessionRequestCache();//在跳转之前,springsecurity将请求缓存在这个httpSessionRequestCache里面
	//实现的跳转的一个类
	private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

	/**
	 * 需要身份认证会跳转到这里
	 * @param request
	 * @param response
	 * @return
	 */
	@RequestMapping("authentication/require")
	@ResponseStatus(code = HttpStatus.UNAUTHORIZED)//不是一个html就返回一个401的状态码
	public SimpleResponse requireAuthentication(HttpServletRequest request, HttpServletResponse response) throws IOException {
		//拿到在跳转之前缓存的请求
		SavedRequest savedRequest = requestCache.getRequest(request, response);
		if (savedRequest != null) {
			String target = savedRequest.getRedirectUrl();
			logger.info("引发跳转的请求是:{}", target);
			if (StringUtils.endsWithIgnoreCase(target, ".html")) {
				//跳转,需要使用到RedirectStrategy
				redirectStrategy.sendRedirect(request,response,securityProperties.getBrowser().getLoginPage());//这里需要实现一个统一,通用的跳转,不仅仅跳到自己的界面。
			}
		}
		return new SimpleResponse("需要用户认证,引导到登录页");
	}
}

demo测试例子,在application.properties里面配置一个flexible.security.browser.loginPage="xxx.html"就可以了。

直接访问请他的路径会出现如下的情况:

输入demo-sigIn.html时就会跳到自定义的登录页

注释掉配置的flexible.security.browser.loginPage="xxx.html"就会跳转默认的登录页。

自定义登录成功处理

需要实现AuthenticationSuccessHandler接口

FlexibleAuthenticationHandler.java

@Component(value = "flexibleAuthenticationHandler")
public class FlexibleAuthenticationHandler implements AuthenticationSuccessHandler {
	Logger logger = LoggerFactory.getLogger(FlexibleAuthenticationHandler.class);
	@Autowired
	ObjectMapper objectMapper;
	/**
	 *
	 * @param httpServletRequest
	 * @param httpServletResponse
	 * @param authentication 封装了认证请求的信息(ip,session,用户信息)
	 * @throws IOException
	 * @throws ServletException
	 */
	@Override
	public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
	logger.info("....login success...");
	httpServletResponse.setContentType("application/json;charset=UTF-8");
	//将authentication对象一json格式返回去
	httpServletResponse.getWriter().write(objectMapper.writeValueAsString(authentication));
	}
}

需要告诉SpringSecurity成功后的处理

  .successHandler(flexibleAuthenticationHandler)//告诉SpringSecurity成功之后使用的处理器

自定义失败处理

需要实现AuthenticationFailureHandler接口

FlexibleAuthenticationFailureHandler.java

@Component(value = "flexibleAuthenticationFailureHandler")
public class FlexibleAuthenticationFailureHandler implements AuthenticationFailureHandler {
	Logger logger = LoggerFactory.getLogger(FlexibleAuthenticationFailureHandler.class);

	@Autowired
	ObjectMapper objectMapper;

	@Override
	public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException authentication) throws IOException, ServletException {

		logger.info("....login failure...");
		httpServletResponse.setContentType("application/json;charset=UTF-8");
		httpServletResponse.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
		//将authentication对象一json格式返回去
		httpServletResponse.getWriter().write(objectMapper.writeValueAsString(authentication));
	}
}

告诉SpringSecurity失败后的处理器:

.failureHandler(flexibleAuthenticationFailureHandler)//告诉SpringSecurity失败后的处理器

记住我的功能的实现

实现的原理剖析

猜你喜欢

转载自my.oschina.net/u/3474937/blog/2962036