SpringSecurity的基本原理
SpringSecurity框架作为一款安全框架,可以拦截我们的请求,默认情况下使用的是basic认证的方式,如图一那样。但是,这种方式很不友好,每次的密码都是动态的变化的,无法自定义,我们可以在application.properties配置security.basic.enabled=false进行关闭。但是此时谁都可以访问我们的接口,很不安全。此时可以在在跟包下创建一个BrowswerSecurityConfig类,集成WebSecurityConfiguerAdapter适配器,此时访问就是如图二那样 http.formLogin()//表示使用表单登录 .and() .authorizeRequests()//表示对请求的授权 .anyRequest()//对任何请求授权 .authenticated();//都需要身份认证
SpringSecurity的原来剖析
SpringSecurity实际上是一系列的过滤器链
1.UsernamepasswordAuthenticationFilter主要负责表单的认证
2.BasicAuthenticationFilter主要负责SpringSecurity默认的basic认证
3.ExceptionTranslationFilter:根据抛出的错误跳转到对应的页面
4.FilterSecurityInterceptor是过滤器类,根据我们的配置是否可以访问后台的接口
自定义用户认证逻辑
1.处理用户信息获取逻辑
要获取用户的信息就需要实现UserDetailsService,并且实现UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;方法
2.处理用户校验逻辑 用户的校验可以使用关联我们的数据库,根据数据库的数据来进行校验,校验之后会返回一个UserDtails对象。这个对象类如下:
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
//用户名
String getPassword();
//密码
String getUsername();
//账户是否超时
boolean isAccountNonExpired();
//账户是否被冻结
boolean isAccountNonLocked();
//密码是否失效
boolean isCredentialsNonExpired();
//是否可用
boolean isEnabled();
}
3.处理密码加密解密 需要在BrowswerSecurityConfig.java配置passwordEncoder加密器
/**
* 配置密码编码器
* [@return](https://my.oschina.net/u/556800)
*/
[@Bean](https://my.oschina.net/bean)
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
在MyUserDetailsService中的代码如下:
[@Component](https://my.oschina.net/u/3907912)
public class MyUserDetailService implements UserDetailsService {
@Autowired
PasswordEncoder passwordEncoder;
private Logger logger = LoggerFactory.getLogger(MyUserDetailService.class);
[@Override](https://my.oschina.net/u/1162528)
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
logger.info("登录用户名:"+s);
String password = passwordEncoder.encode("123");
logger.info("用户密码:"+password);
return new User(s,password/*"123"*/,
true,true,true,true,
AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}
}
个性化用户认证流程
1.自定义登录页面 自定义登录页面需要在代码中配置如下的代码:
http
.formLogin()//表示使用表单登录
.loginPage("/imooc-signIn.html")//表示条状登录页面
.and()
.authorizeRequests()//表示对请求的授权
.anyRequest()//对任何请求授权
.authenticated();//都需要身份认证
但是如果这样以访问会出问题,因为即使是访问登录页面,也需要登录认证,会出现多次访问的情况:
此时代码还需要放权,添加的代码如下:
.antMatchers("/imooc-signIn.html")
.permitAll()
加了这个放行的代码之后再次访问会出现如下的显示:
默认情况下的UsernamepasswordAthenticationFilter过滤的路径是/login,源码:
public UsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher("/login", "POST"));
}
为了让SpringSecurity能够过滤我们自定义的路径,如我们表单的路径/authentication/form,需要在BrowswerSecurityConfig.java内配置如下:
.loginProcessingUrl("/authentication/form")//表示让过滤器处理我们自定义的路径的
此时在此登录会发现如下情况(因为SpringSecurity提供了跨站访问伪造的一个防护):
此时可以想关闭这个防护
.and()
.csrf().disable();//关闭跨站伪造的防护
处理不同类型的请求
需要添加一个controller,代码如下所示:
@RestController
public class BrowserSecurityController {
private Logger logger = LoggerFactory.getLogger(BrowserSecurityController.class);
//请求缓存
private RequestCache requestCache = new HttpSessionRequestCache();
//调转工具
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
@Autowired
SecurityProperties securityProperties;
/**
* 当需要省份认证时跳转到这里
*
* @param request
* @param response
* @return
*/
@RequestMapping("/authentcation/require")
@ResponseStatus(code = HttpStatus.UNAUTHORIZED)
public SimpleResponse requireAuthentication(HttpServletRequest request, HttpServletResponse response) throws IOException {
//拿到引发跳转的请求
SavedRequest savedRequest = requestCache.getRequest(request, response);
if (savedRequest != null) {
String targetUrl = savedRequest.getRedirectUrl();
logger.info("引发跳转的请求是:" + targetUrl);
//如果请求的是html,那么就直接到登录的html
if (StringUtils.endsWithIgnoreCase(targetUrl, ".html")) {
redirectStrategy.sendRedirect(request, response, securityProperties.getBrowser().getLoginPage());
}
}
return new SimpleResponse("访问的服务需要省份认证,请引导用户到登录页面");
}
}
除了添加上年的controller代码之外还需要在配置添加如下的代码:
http
// .httpBasic()//使用的是SpringSecurity的基本认证
.formLogin()//表示使用表单登录
.loginPage("/authentcation/require")//通过controller去处理不同类型的请求
.loginProcessingUrl("/authentication/form")//表示让过滤器处理我们自定义的路径的
.and()
.authorizeRequests()//表示对请求的授权
//表示对登录的页面允许访问,不需要授权
.antMatchers("/authentcation/require",securityProperties.getBrowser().getLoginPage())
.permitAll()
.anyRequest()//对任何请求授权
.authenticated()//都需要身份认证
.and()
.csrf().disable();//关闭跨站伪造的防护
2.自定义登录成功处理 需要实现AuthenticationSuccessHandler接口
@Component("imoocAuthenticationSuccessHandler")
public class ImoocAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private Logger logger = LoggerFactory.getLogger(ImoocAuthenticationSuccessHandler.class);
@Autowired
private ObjectMapper objectMapper;
/**
*
* @param request
* @param response
* @param authentication 包装了所有的关于用户的信息
* @throws IOException
* @throws ServletException
*/
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
logger.info("登录成功");
response.setContentType("application/json;charset=UTF-8");
//将authentication以json字符串显示回去
response.getWriter().write(objectMapper.writeValueAsString(authentication));
}
}
此时已经完成了自定义的的登录成功的处理器的编写,但是仍然无法使用,需要注册进去
/**
* 专门用来做web安全应用的适配器WebSecurityConfigurerAdapter
*/
@Configuration
public class BrowswerSecurityConfig extends WebSecurityConfigurerAdapter{
@Autowired
SecurityProperties securityProperties;
/**
* 登录成功处理器
*/
@Autowired
ImoocAuthenticationSuccessHandler imoocAuthenticationSuccessHandler;
/**
* 登录失败处理器
*/
@Autowired
ImoocAuthenticationFailureHandler imoocAuthenticationFailureHandler;
/**
* 配置密码编码器
* @return
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// .httpBasic()//使用的是SpringSecurity的基本认证
.formLogin()//表示使用表单登录
.loginPage("/authentcation/require")//表示条状登录页面
.loginProcessingUrl("/authentication/form")//表示让过滤器处理我们自定义的路径的
.successHandler(imoocAuthenticationSuccessHandler)//注册我们的登录成功的处理器
.failureHandler(imoocAuthenticationFailureHandler)//注册我们自定义的登录失败处理器
.and()
.authorizeRequests()//表示对请求的授权
//表示对登录的页面允许访问,不需要授权
.antMatchers("/authentcation/require",securityProperties.getBrowser().getLoginPage())
.permitAll()
.anyRequest()//对任何请求授权
.authenticated()//都需要身份认证
.and()
.csrf().disable();//关闭跨站伪造的防护
}
}
此时登录访问,登录成功会出现如下的表现:
3.自定义登录失败处理
需要实现AuthenticationFailureHandler接口
@Component("imoocAuthenticationFailureHandler")
public class ImoocAuthenticationFailureHandler implements AuthenticationFailureHandler {
private Logger logger = LoggerFactory.getLogger(ImoocAuthenticationFailureHandler.class);
@Autowired
private ObjectMapper objectMapper;
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
logger.info("登录失败");
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());//设置服务器错误
//将authentication以json字符串显示回去
response.getWriter().write(objectMapper.writeValueAsString(exception));
}
}
完成了处理器的代码编写统一需要注册,登录访问失败会有如下的返回值:
上面的自定义处理器存在的问题
前端是表单提交的时候可能就需要直接跳转到登录之前的路径,此时就需要使用跳转,否则就返回json合适
为了实现可以给用户配置,在配置类添加一个登录的类型
public enum LoginType {
REDIRECT,//跳转
JSON//返回json
}
public class BrowserProperties {
private String loginPage = "/imooc-signIn.html";
private LoginType loginType = LoginType.JSON;
public BrowserProperties() {
}
public String getLoginPage() {
return loginPage;
}
public void setLoginPage(String loginPage) {
this.loginPage = loginPage;
}
public LoginType getLoginType() {
return loginType;
}
public void setLoginType(LoginType loginType) {
this.loginType = loginType;
}
}
在登录成功失败添加登录类型判断:
if (LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())) {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());//设置服务器错误
//将authentication以json字符串显示回去
response.getWriter().write(objectMapper.writeValueAsString(exception));
}else {
}
在代码添加好这些功能之后就可以在配置文件配置:
imooc.security.browser.loginType=REDIRECT