简介
(1)Spring Security是一个专注于为Java应用程序提供身份认证和授权的框架,它的强大之处在于它可以轻松扩展以满足自定义的需求。
(2)官网在https://spring.io/projects/spring-security
(3)推荐资料:www.spring4all.com
特征
(1)对身份的认证和授权提供全面的、可扩展的支持。
(2)防止各种攻击,如会话固定攻击、点击劫持、crf攻击等。
(3)支持与Service API、Spring MVC等Web技术集成。
原理示意图(spring-security是基于filter)
在mvn库里搜索spring security,把spring-boot-starter-security复制到pom.xml。在User类里添加
// true:账号未过期(在此不做过期处理)
@Override
public boolean isAccountNonExpired() {
return true;
}
// true:账号未锁定
@Override
public boolean isAccountNonLocked() {
return true;
}
// true:凭证未过期
@Override
public boolean isCredentialsNonExpired() {
return true;
}
//true:账号可用
@Override
public boolean isEnabled() {
return true;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> list = new ArrayList<>();
list.add(new GrantedAuthority() {
@Override
public String getAuthority() {
switch (type){
case 1:
return "ADMIN";
default:
return "USER";
}
}
});
return list;
}
在UserService类里,实现UserDetailService接口,并添加
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return this.findUserByName(username);
}
新建SecurityConfig类
@Override
public void configure(WebSecurity web) throws Exception {
// super.configure(web);
//忽略静态资源的访问
web.ignoring().antMatchers("/resources/**"); // /resources/**主要是图片、js、css等静态资源,不需要拦截
}
// AuthenticationManager:认证的核心接口
// AuthenticationManagerBuilder:用于构建AuthenticationManager对象的工具
// ProviderManager:AuthenticationManager接口默认实现类
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// super.configure(auth);
// 内置的认证规则
// auth.userDetailsService(userService).passwordEncoder(new Pbkdf2PasswordEncoder("12345")); //后面这个字符串是加的盐但这个盐和我们系统原来的盐不匹配
// 自定义认证规则
// AuthenticationProvider:ProviderManager(一个集合)持有一组AuthenticationProvider,每个AuthenticationProvider负责一种认证
// 委托模式:ProviderManager将认证委托给AuthenticationProvider。
auth.authenticationProvider(new AuthenticationProvider() {
// Authentication:用于封装认证信息的接口,不同的实现类代表不同类型的认证信息(如:账号密码/qq/微信/人脸/指纹信息,这里用的是【账号密码】)。
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = (String) authentication.getCredentials();
User user = userService.findUserByName(username);
if(user==null){
throw new UsernameNotFoundException("账号不存在!");
}
password = CommunityUtil.md5(password+user.getSalt());
if(!user.getPassword().equals(password)){
throw new BadCredentialsException("密码不正确!");
}
// principal:主要信息:credentials:证书;authorities:权限
return new UsernamePasswordAuthenticationToken(user,user.getPassword(),user.getAuthorities());
}
//当前的AuthenticationProvider接口支持的认证类型(这里是【账号密码】类型)
@Override
public boolean supports(Class<?> aClass) {
// UsernamePasswordAuthenticationToken:Authentication接口常用的实现类。
return UsernamePasswordAuthenticationToken.class.equals(aClass);
}
});
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// super.configure(http); //避开父类默认的登录页面
// 登录相关配置
http.formLogin()
.loginPage("/loginpage")
.loginProcessingUrl("/login")
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.sendRedirect(request.getContextPath()+"/index");
}
})
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
//这里不能用重定向,要用转发
request.setAttribute("error",e.getMessage());
request.getRequestDispatcher("/loginpage").forward(request,response);
}
});
// 退出相关配置
http.logout()
.logoutUrl("/logout")
.logoutSuccessHandler(new LogoutSuccessHandler() {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.sendRedirect(request.getContextPath()+"/index");
}
});
// 授权配置
http.authorizeRequests()
.antMatchers("/letter").hasAnyAuthority("USER","ADMIN")
.antMatchers("/admin").hasAnyAuthority("ADMIN") //只有"ADMIN"才有权限访问"/admin"页面
.and().exceptionHandling().accessDeniedPage("/denied");
// 增加filter,处理验证码
http.addFilterBefore(new Filter() {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
if(request.getServletPath().equals("/login")){
String verifyCode = request.getParameter("verifyCode");
if(verifyCode==null||!verifyCode.equalsIgnoreCase("1234")){
request.setAttribute("error","验证码错误!");
request.getRequestDispatcher("/loginpage").forward(request,response);
return;
}
}
//让请求继续向下执行,如果后面还有多个filter的话
filterChain.doFilter(request,response);
}
}, UsernamePasswordAuthenticationFilter.class);
// 记住我,存到内存里,若要存到redis的话另外实现
http.rememberMe()
.tokenRepository(new InMemoryTokenRepositoryImpl())
.tokenValiditySeconds(3600*24)
.userDetailsService(userService); //查出用户信息自动帮你过
}`
在HomeController类里,添加
@RequestMapping(path = "/index", method = RequestMethod.GET)
public String getIndexPage(Model model) {
// 认证成功后,结果会通过SecurityContextHolder存入SecurityContext中
Object obj = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if(obj instanceof User){
model.addAttribute("loginUser",obj);
}
return "/index";
}
// 拒绝访问时的提示页面(或者说没有权限时)
@RequestMapping(path = "/denied", method = RequestMethod.GET)
public String getDeniedPage(){
return "/error/404";
}
对login.html和index.html作相应修改。