spring security 的核心功能主要包括:
认证 (你是谁)
授权 (你能干什么)
攻击防护 (防止伪造身份)
其核心就是一组过滤器链,项目启动后将会自动配置。最核心的就是 Basic Authentication Filter 用来认证用户的身份,一个在spring security中一种过滤器处理一种认证方式。
实现基本思路:服务端通过 JSON字符串,告诉前端用户有没有登录、认证,前端根据这些提示跳转对应的登录页、认证页等。
package com.grsoft.security.controller;
import java.io.Serializable;
/**
* @Description: http返回结果
* @author: : Steven
* @Date: 2020/2/13 16:29
*/
public class ResponseBody implements Serializable {
private String status;
private String msg;
private Object result;
private String jwtToken;
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getResult() {
return result;
}
public void setResult(Object result) {
this.result = result;
}
public String getJwtToken() {
return jwtToken;
}
public void setJwtToken(String jwtToken) {
this.jwtToken = jwtToken;
}
}
用户没有登录时返回给前端的数据
package com.grsoft.security.common;
import com.alibaba.fastjson.JSON;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @Description: 未登录
* @author: : Steven
* @Date: 2020/2/13 16:54
*/
public class AjaxAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
AjaxResponseBody responseBody = new AjaxResponseBody();
responseBody.setStatus("000");
responseBody.setMsg("Need Authorities! 未登录");
httpServletResponse.getWriter().write(JSON.toJSONString(responseBody));
}
}
用户登录失败时返回给前端的数据
package com.grsoft.security.common;
import com.alibaba.fastjson.JSON;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @Description: 登录失败
* @author: : Steven
* @Date: 2020/2/13 16:55
*/
public class AjaxAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
AjaxResponseBody responseBody = new AjaxResponseBody();
responseBody.setStatus("400");
responseBody.setMsg("Login Failure! 登录失败");
httpServletResponse.getWriter().write(JSON.toJSONString(responseBody));
}
}
用户登录成功时返回给前端的数据
package com.grsoft.security.common;
import com.alibaba.fastjson.JSON;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @Description: 登录成功
* @author: : Steven
* @Date: 2020/2/13 16:56
*/
public class AjaxAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
AjaxResponseBody responseBody = new AjaxResponseBody();
responseBody.setStatus("200");
responseBody.setMsg("Login Success! 登录成功");
httpServletResponse.getWriter().write(JSON.toJSONString(responseBody));
}
}
登录成功但无权访问
package com.grsoft.security.common;
import com.alibaba.fastjson.JSON;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @Description: 登录成功但无权访问
* @author: : Steven
* @Date: 2020/2/13 16:58
*/
public class AjaxAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
AjaxResponseBody responseBody = new AjaxResponseBody();
responseBody.setStatus("300");
responseBody.setMsg("Need Authorities! 登录成功但无权访问");
httpServletResponse.getWriter().write(JSON.toJSONString(responseBody));
}
}
package com.grsoft.security.common;
import com.alibaba.fastjson.JSON;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class AjaxLogoutSuccessHandler implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
AjaxResponseBody responseBody = new AjaxResponseBody();
responseBody.setStatus("100");
responseBody.setMsg("Logout Success!");
httpServletResponse.getWriter().write(JSON.toJSONString(responseBody));
}
}
判断授权有效性,用户有效性,在判断用户是否有效性,它依赖于UserDetailsService实例
package com.grsoft.security.authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.io.Serializable;
import java.util.Collection;
import java.util.Set;
public class SelfUserDetails implements UserDetails, Serializable {
private String username;
private String password;
private Set extends GrantedAuthority> authorities;
@Override
public Collection extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
public void setAuthorities(Set extends GrantedAuthority> authorities) {
this.authorities = authorities;
}
// 最重点Ⅰ
@Override
public String getPassword() {
return this.password;
}
// 最重点Ⅱ
@Override
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
package com.grsoft.security.authentication;
import org.springframework.security.authentication.encoding.Md5PasswordEncoder;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.Set;
@Component
public class SelfUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//构建用户信息的逻辑(从数据库取用户信息)
SelfUserDetails userInfo = new SelfUserDetails();
userInfo.setUsername(username);
Md5PasswordEncoder md5PasswordEncoder = new Md5PasswordEncoder();
String encodePassword = md5PasswordEncoder.encodePassword("123", username); // 模拟从数据库中获取的密码原为 123
userInfo.setPassword(encodePassword);
Set authoritiesSet = new HashSet();
GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_ADMIN"); // 模拟从数据库中获取用户角色
authoritiesSet.add(authority);
userInfo.setAuthorities(authoritiesSet);
return userInfo;
}
}
package com.grsoft.security.authentication;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.encoding.Md5PasswordEncoder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
@Component
public class SelfAuthenticationProvider implements AuthenticationProvider {
@Autowired
SelfUserDetailsService userDetailsService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String userName = (String) authentication.getPrincipal(); // 这个获取表单输入中返回的用户名;
String password = (String) authentication.getCredentials(); // 这个是表单中输入的密码;
Md5PasswordEncoder md5PasswordEncoder = new Md5PasswordEncoder();
String encodePwd = md5PasswordEncoder.encodePassword(password, userName);
//1.从数据库中取出用户的信息
UserDetails userInfo = userDetailsService.loadUserByUsername(userName);
//2.把Form输入的密码和数据库的密码对比
if (!userInfo.getPassword().equals(encodePwd)) {
throw new BadCredentialsException("用户名密码不正确,请重新登陆!");
}
return new UsernamePasswordAuthenticationToken(userName, password, userInfo.getAuthorities());
}
@Override
public boolean supports(Class> authentication) {
return true;
}
}
package com.grsoft.security.common;
import com.grsoft.security.authentication.SelfAuthenticationProvider;
import com.grsoft.security.filter.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
public class MyWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Autowired
AjaxAuthenticationEntryPoint authenticationEntryPoint; // 未登陆时返回 JSON 格式的数据给前端(否则为 html)
@Autowired
AjaxAuthenticationSuccessHandler authenticationSuccessHandler; // 登录成功返回的 JSON 格式数据给前端(否则为 html)
@Autowired
AjaxAuthenticationFailureHandler authenticationFailureHandler; // 登录失败返回的 JSON 格式数据给前端(否则为 html)
@Autowired
AjaxLogoutSuccessHandler logoutSuccessHandler; // 注销成功返回的 JSON 格式数据给前端(否则为 登录时的 html)
@Autowired
AjaxAccessDeniedHandler accessDeniedHandler; // 无权访问返回的 JSON 格式数据给前端(否则为 403 html 页面)
@Autowired
SelfAuthenticationProvider provider; // 自定义安全认证
@Autowired
JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; // JWT 拦截器
/**
* @Description: 配置Security的认证信息, 用户身份的管理者, 是认证的入口
* @author: : Steven
* @Date: 2020/2/17 18:46
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//方式1
// 加入自定义的安全认证
auth.authenticationProvider(provider);
//还有其他两种方式如下:
//方式2
//auth.inMemoryAuthentication().withUser("admin").password("admin").roles("USER");
//方式3
//auth.userDetailsService(dbUserDetailsService);
}
/**
* @Description: 配置Security的认证策略
* @author: : Steven
* @Date: 2020/2/17 18:46
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()// 禁用跨站攻击
.httpBasic().authenticationEntryPoint(authenticationEntryPoint)
.and()
.authorizeRequests()
// .antMatchers("/login", "/authentication/form").permitAll() //自定义不进行认证的页面
.anyRequest()
.authenticated()// 其他 url 需要身份认证
.and()
.formLogin() //开启登录
// .loginPage("/myLogin.html")// 自定义登录页面
// .loginProcessingUrl("/login.do")// 自定义登录controller
.successHandler(authenticationSuccessHandler) // 登录成功
.failureHandler(authenticationFailureHandler) // 登录失败
.permitAll()
.and()
.logout()
.logoutSuccessHandler(logoutSuccessHandler)
.permitAll();
http.exceptionHandling().accessDeniedHandler(accessDeniedHandler); // 无权访问 JSON 格式的数据
//加入过滤器
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
@Override
public void configure(WebSecurity web) throws Exception {
//指定忽略的静态资源
web.ignoring().antMatchers("/static/**");
}
}
HttpSecurity使用说明
方法 |
说明 |
openidLogin() |
用于基于 OpenId 的验证 |
headers() |
将安全标头添加到响应 |
cors() |
配置跨域资源共享( CORS ) |
sessionManagement() |
允许配置会话管理 |
portMapper() |
允许配置一个PortMapper(HttpSecurity#(getSharedObject(class))),其他提供SecurityConfigurer的对象使用 PortMapper 从 HTTP 重定向到 HTTPS 或者从 HTTPS 重定向到 HTTP。默认情况下,Spring Security使用一个PortMapperImpl映射 HTTP 端口8080到 HTTPS 端口8443,HTTP 端口80到 HTTPS 端口443 |
jee() |
配置基于容器的预认证。 在这种情况下,认证由Servlet容器管理 |
x509() |
配置基于x509的认证 |
rememberMe |
允许配置“记住我”的验证 |
authorizeRequests() |
允许基于使用HttpServletRequest限制访问 |
requestCache() |
允许配置请求缓存 |
exceptionHandling() |
允许配置错误处理 |
securityContext() |
在HttpServletRequests之间的SecurityContextHolder上设置SecurityContext的管理。 当使用WebSecurityConfigurerAdapter时,这将自动应用 |
servletApi() |
将HttpServletRequest方法与在其上找到的值集成到SecurityContext中。 当使用WebSecurityConfigurerAdapter时,这将自动应用 |
csrf() |
添加 CSRF 支持,使用WebSecurityConfigurerAdapter时,默认启用 |
logout() |
添加退出登录支持。当使用WebSecurityConfigurerAdapter时,这将自动应用。默认情况是,访问URL”/ logout”,使HTTP Session无效来清除用户,清除已配置的任何#rememberMe()身份验证,清除SecurityContextHolder,然后重定向到”/login?success” |
anonymous() |
允许配置匿名用户的表示方法。 当与WebSecurityConfigurerAdapter结合使用时,这将自动应用。 默认情况下,匿名用户将使用org.springframework.security.authentication.AnonymousAuthenticationToken表示,并包含角色 “ROLE_ANONYMOUS” |
formLogin() |
指定支持基于表单的身份验证。如果未指定FormLoginConfigurer#loginPage(String),则将生成默认登录页面 |
oauth2Login() |
根据外部OAuth 2.0或OpenID Connect 1.0提供程序配置身份验证 |
requiresChannel() |
配置通道安全。为了使该配置有用,必须提供至少一个到所需信道的映射 |
httpBasic() |
配置 Http Basic 验证 |
addFilterAt() |
在指定的Filter类的位置添加过滤器 |
在浏览器输入http://localhost:8080/index 输入错误的密码:
登录:执行过程基本是这样的:a.Form登录login---->b.AuthenticationProvider.()返回Authentication对象----->c.根据上一步返回的结果判断进入AuthenticationSuccessHandler还是AuthenticationFailureHandler