1. 在maven中加载了SpringSecurity相关的jar包,spring默认会开启一些配置,
可通过在application中配置security.basic.enabled=false关闭。
2.默认的密码会在日志中输出
3.默认的方式通常不能满足需求,需要对springsecurity进行配置
SpringSecurity基本原理
绿色(最核心):
用来认证用户的身份,一个方块代表一个过滤器,每一个过滤器负责处理一种认证方式;主要工作是检查当前的请求里面是不是有这个过滤器所需要的信息;对于UsernamePasswordAuthenticationFilter这个过滤器来说,首先检查当前的请求是不是一个登录请求,该请求中是否携带用户名和密码,如果带了,则该过滤器则尝试用用户名和密码作用户的登录,如果不带,则放行,到下一个过滤器。HTTPBasic过滤器会检查请求的请求头中是否有basic开头的Authentication信息,如果有,则会尝试拿出来作base64解码,然后取出用户名和密码尝试登录。按照此种方式一直往下走;任何一个过滤器成功完成了用户登录之后会在请求上做一个标记:当前用户已经认证成功了。
橙色(FilterSecurityInterceptor):
请求经过了绿色的过滤器之后,到达了该过滤器,该过滤器是整个过滤器链的最后一环,该过滤器决定了当前的请求能不能访问后面真正的服务。判断依据:
.anyRequest()//任何请求
.authenticated();//都需要身份认证
那么他就会判断当前的请求是否经过了前面某一个过滤器的身份认证,判断的结果就是过还是不过,如果过了,就到达服务;如果不过,会根据不同的原因抛出不同的异常。例如配置的是所哟请求都要经过身份认证,如果没有经过身份认证,则会抛一个没有身份认证的异常;如果配的是VIP用户才能访问,虽然经过了身份认证了,但是不是VIP用户,则会抛一个没权限的异常。会根据不能访问的原因来抛出不同的异常。
在异常抛出来以后,在该过滤器的前边还有一个过滤器Exception Translation Filter
蓝色(Exception Translation Filter):
该过滤器的作用是用来捕获后面的过滤器所抛出来的异常;会根据抛出来的异常作相应的处理,比如因为没有登录不能访问,则会根据绿色的过滤器的配置引导用户去登录,比如前面配的是UsernamePasswordAuthenticationFilter,则会把用户引导到一个登录页面中去;前面配的是Basic Authentication Filter,那么就会在浏览器弹出一个窗口,要用户输入用户名和密码。
在实际业务中,仅根据默认的密码是不符合要求的,在Springsecurity中处理登录的逻辑放在UserDetailService接口中,
可以继承这个接口,在loadUserByUserName中实现自己的逻辑处理。具体代码如下
@Component
public class MyUserDetailsService implements UserDetailsService{
private static Logger logger = LoggerFactory.getLogger( MyUserDetailsService.class);
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
String password = passwordEncoder.encode("123456");
logger.info("用户名为{}的用户,用密码{}登录",userName,password);
/*
1.用户是否失效
2.用户账号是否过期
3.用户凭证是否过期
4.账号是否被锁定
boolean isAccountNonExpired();账户没有过期(true:没有过期;false:过期了)
boolean isAccountNonLocked();账户是否锁定
boolean isCredentialsNonExpired();密码是否过期()
boolean isEnabled();账户是否可用
*/
return new User(userName,password,true,true,true,true, AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}
}
在实际开发中,往往需要自己指定页面,可以用loginPage()指定能陆界面
http.formLogin() //表单登录
.loginPage("/reed-signIn.html") //指定登录界面
但运行后会出现如下情况,因为所有的请求都将被springsecurity拦截去登陆,因此会无限循环重定向。
可以在后面加上如下如下配置
.authorizeRequests() //表示以下都是授权的配置
.antMatchers("/reed-signIn.html").permitAll()//访问该url不需要身份认证
此时,跳转到登录界面的请求将不会被拦截
---
springsecurity中的UsernamePasswordAuthenticationFilter这个类负责处理表单登录请求。
从源码可以看出,springsecurity默认处理的是/login这个请求
如果我们想改为自定义的请求,比如将路径改为/authentication/form,需做如下的配置
首先加上loginProcessingUrl("/authentication/form"),
http.formLogin() //表单登录
.loginPage("/reed-signIn.html") //指定登录界面
.loginProcessingUrl("/authentication/form")
修改后还要暂时关闭xsrf
.and()
.csrf().disable(); //暂时将跨域请求伪造的功能去掉
---
改进:处理不同类型的请求
把上面直接是跳转到一个页面,换成一个Controller,让Controller判断是否是一个HTML请求引发的跳转,如果是就返回登录页面如果不是就返回401状态码和错误信息:
可进行如下改造
修改.loginPage("/authentication/require"),然后用一个controller接收这个请求
@RestController
public class BrowserSecurityController {
private Logger logger = LoggerFactory.getLogger(getClass());
// 将请求缓存到这个session里面
private RequestCache requestCache = new HttpSessionRequestCache();
//处理跳转用
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
@Autowired
private SecurityProperties securityProperties;
/*
当需要身份认证时跳转到这里
*/
@RequestMapping("/authentication/require")
@ResponseStatus(code = HttpStatus.UNAUTHORIZED)
public CommonRet requireAuthentication(HttpServletRequest request, HttpServletResponse response)throws IOException{
CommonRet commonRet = new CommonRet();
SavedRequest savedRequest = requestCache.getRequest(request,response);
if(savedRequest != null){
String targetUrl = savedRequest.getRedirectUrl();
logger.info("引发跳转的请求是:{}",targetUrl);
//如果引发跳转的以html结尾,就是一个页面访问
if(StringUtils.endsWithIgnoreCase(targetUrl,".html")){
redirectStrategy.sendRedirect(request,response,securityProperties.getBrowserProperties().getLoginPage());
}
}
commonRet.setMsg("访问该服务需要认真,请引导用户到登录页");
return commonRet;
}
}
------
自定义登录成功处理
场景:默认情况下,SpringSecurity的登录成功的处理会首先跳到之前引发登录的请求上,比如访问/user,需要身份认证,就会跳转到登录页,登录成功了,又会跳回user请求上。但是在现在前端spa比较流行的情况下,登录可能不是一个表单提交的同步方式,而是由异步的ajax请求访问登录。此时,前端想要拿到的是用户相关的json格式的信息,此时如果登录成功了进行跳转,此种行为肯定是不合适的。
实现AuthenticationSuccessHandler接口即可。
@Component("reedAuthenticationSuccessHandler")
public class ReedAuthenticationSuccessHandler implements AuthenticationSuccessHandler{
@Autowired
private static Logger logger = LoggerFactory.getLogger(ReedAuthenticationSuccessHandler.class);
@Autowired
private ObjectMapper objectMapper;
/*
此方法在登录成功的时候讲会被调用
*/
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
logger.info("登录成功");
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(authentication));
}
}
然后在配置中注入这个bean并加上.successHandler(reedAuthenticationSuccessHandler)
同理:失败的时候 implements AuthenticationFailureHandler
-----