1.判断当前请求是否需要身份认证 没有返回401
2.判断有没有权限 没有返回403
为防止大流量把系统压死,一般在反向代理和负载均衡处处理。简单的限流器:
com.google.guava
guava
28.0-jre
/**
* @author aric
* @create 2021-03-30-18:00
* @fun 限流器
*/
//继承OncePerRequestFilter过滤只会过滤一次
@Component
@Order(1) //执行顺序
public class RateLimitFilter extends OncePerRequestFilter {
private RateLimiter rateLimiter = RateLimiter.create(1); //1s允许1个连接的过滤器
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if(rateLimiter.tryAcquire()){
//没达到限流
filterChain.doFilter(request,response);
}else{
//太多请求
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
response.getWriter().write("too many request!!!");
response.getWriter().flush();
return;
}
}
}
不管有没有成功,都继续进行
HttpBasic认证:
pom文件
org.apache.commons
commons-lang3
BasicAuthecationFilter.java:
/**
* @author aric
* @create 2021-03-30-19:20
* @fun 认证字符串过滤器 一般开发中不用,调用密码加密验证很耗CPU,一般用TOKEN
*/
@Component
@Order(2) //执行顺序
public class BasicAuthecationFilter extends OncePerRequestFilter {
@Autowired
private UserRepository userRepository;
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
//只有请求头带有认证信息才能进入
if(StringUtils.isNotBlank(authHeader)){
//拿到编译后的认证后的字符串
String token64 = StringUtils.substringAfterLast(authHeader,"Basic ");
String token = new String(Base64Utils.decodeFromString(token64));
//根据“:”拆成2个字符串
String [] items = StringUtils.splitByWholeSeparatorPreserveAllTokens(token,":");
String username = items[0];
String password = items[1];
User user = userRepository.findByUsername(username);
if(user != null && StringUtils.equals(password,user.getPassword())){
request.setAttribute("user",user);
}
}
//请求头没有携带认证信息,继续往下走
filterChain.doFilter(request,response);
}
}
注:测试时发送请求头部必须携带认证信息
Controller.java:
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public UserInfo get(@PathVariable Long id,HttpServletRequest request){
//用户只能获取自己的用户信息
User user = (User)request.getAttribute("user");
if(user == null || !user.getId().equals(id)){
throw new RuntimeException("身份认证信息异常,获取用户信息失败。");
}
return userService.get(id); //做查询操作即可
}
}
一般在接口层面或数据库层面。
@NotBlank(message = "用户名不能为空") //为空校验 应用层面
@Column(unique = true) //不能重复 数据库层面,会改数据库配置
private String username;
@PostMapping
public UserInfo create(@RequestBody @Validated UserInfo user){ //@Validated:认证规则生效注解
return userService.create(user);
}
如果输入密码明文能加密成密文串则成功,不能再解密成明文,再用随机字符串做盐加在密文串中。
com.lambdaworks
scrypt
1.4.0
@Component
public class BasicAuthecationFilter extends OncePerRequestFilter {
/********
if(user != null && SCryptUtil.check(password,user.getPassword())){ //加密后的密码验证
request.setAttribute("user",user);
}
********/
}
public class UserServiceImpl implements UserService {
public UserInfo create(UserInfo info) {
User user = new User();
BeanUtils.copyProperties(info,user);
//加密,参数主要是控制加密位数,控制CPU使用频率
user.setPassword(SCryptUtil.scrypt(user.getPassword(),32768,8,1));
userRepository.save(user);
info.setId(user.getId());
return info;
}
}
可以验证双方的身份,在数据传输的过程中,对数据进行记录,封装,加密。
# //Https安全,RSA加密配置
server:
ssl:
key-store: classpath:xuyu.key
key-store-password: 123456
key-password: 123456
日志一定要持久化。
一般拦截器关系:
日志类:
/**
* @author aric
* @create 2021-03-31-11:32
* @fun
*/
@EntityListeners(AuditingEntityListener.class) //增加监听器
@Data
public class AuditLog {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String method;
private String path;
private Integer status;
@CreatedBy //获取上传人 须在SecurityConfig中配置
private String username;
@Temporal(TemporalType.TIMESTAMP) //时间戳注解
@CreatedDate //创建时间注解,jpa做判断
private Date createdTime;
@Temporal(TemporalType.TIMESTAMP)
@LastModifiedDate
private Date modifyTime;
}
日志Jpa
/**
* @author aric
* @create 2021-03-31-11:37
* @fun
*/
public interface AuditLogRepository extends JpaSpecificationExecutor, CrudRepository {
}
记录日志的拦截器:
/**
* @author aric
* @create 2021-03-31-17:23
* @fun 记录日志
*/
@Component
public class AuditLogInterceptor extends HandlerInterceptorAdapter {
@Autowired
private AuditLogRepository auditLogRepository;
//之前记录日志
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler) throws Exception{
AuditLog log = new AuditLog();
log.setMethod(request.getMethod());
log.setPath(request.getRequestURI());
User user = (User) request.getAttribute("user");
if(user != null){
log.setUsername(user.getUsername());
}
auditLogRepository.save(log);
request.setAttribute("auditLogId",log.getId());
return true; //返回false不会被执行
}
//postHandler是处理成功才会调用,不要覆盖
//afterCompletion不管处理成功与否都会调用
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
Long auditLofId = (Long)request.getAttribute("auditLofId");
AuditLog log = auditLogRepository.findById(auditLofId).get();
log.setStatus(response.getStatus());
auditLogRepository.save(log);
}
}
错误处理接口:
/**
* @author aric
* @create 2021-03-31-18:12
* @fun 接管错误处理(比如返回状态码200的错误处理),不再走spring默认的处理
*/
@RestControllerAdvice
public class ErrorHandler {
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) //将所有异常代码转化为服务器异常
@ExceptionHandler(Exception.class)
public Map handler(Exception ex){
HashMap info = new HashMap<>();
info.put("message",ex.getMessage());
info.put("time",new Date().getTime());
return info;
}
}
Security配置类:
/**
* @author aric
* @create 2021-03-31-17:56
* @fun 让AuditLogInterceptor生效
*/
@Configuration
@EnableJpaAuditing //总开关
public class SecurityConfig implements WebMvcConfigurer {
@Autowired
private AuditLogInterceptor auditLogInterceptor;
//执行顺序:先add先执行
@Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(auditLogInterceptor); //还可以add属性只对某些请求生效
}
//在AuditLog中获取上传人 ->注解@CreatedBy实现
@Bean
public AuditorAware auditorAware(){
return new AuditorAware() {
@Override
public Optional getCurrentAuditor() {
return Optional.of("xuyu");
}
};
}
}
登录安全:
优点:提升用户体验,比在客户端只保存信息安全,真正的信息保存在服务器,所有的内容都在Servlet容器中实现好了,只需要getSession获取和SetSession就好了
缺点:只针对浏览器才支持,不支持app和第三方服务,当服务器传送cookie时会被劫持,不是很安全,服务器一般不是一台,如果请求落在另一台,需要重新登录
登录Controller:
/**
* @author aric
* @create 2021-03-30-16:08
* @fun
*/
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/login")
public void login(@Validated UserInfo user,HttpServletRequest request) throws IOException{
UserInfo info = userService.login(user);
request.getSession().setAttribute("User",info);
}
@GetMapping("/logout")
public void logout(@Validated UserInfo user,HttpServletRequest request){
request.getSession().invalidate();
}
}
登录拦截器:
/**
* @author aric
* @create 2021-03-31-18:50
* @fun
*/
@Component
public class AclInterceptor extends HandlerInterceptorAdapter {
private String[] permitUrls = new String[] {"/users/login"}; //授权数组,不能拦截登录
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handle)throws Exception{
boolean result = true; //访问权限
//如果请求路径不再授权数组中
if(!ArrayUtils.contains(permitUrls,request.getRequestURL())){
User user = (User)request.getAttribute("user");
if(user == null){
response.setContentType("text/plain");
response.getWriter().write("need authentication");
response.setStatus(HttpStatus.UNAUTHORIZED.value());
}else{
String method = request.getMethod();
if(!user.hasPermission(method)){
response.setContentType("text/plain");
response.getWriter().write("forbidden");
response.setStatus(HttpStatus.FORBIDDEN.value());
result = false;
}
}
}
return result;
}
}
User类:
/**
* @author aric
* @create 2021-03-30-16:06
* @fun
*/
@Entity //jpa和数据库表绑定
@Data
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) //主键生成策略
private Long id;
private String name;
private String username;
private String password;
private String permissions;
public UserInfo buildInfo(){
UserInfo info = new UserInfo();
BeanUtils.copyProperties(this,info);
return info;
}
public boolean hasPermission(String method) {
boolean result = false;
if(StringUtils.equalsIgnoreCase("get",method)){
result = StringUtils.contains(permissson,"r");
}else{
result = StringUtils.contains(permission,"w");
}
return result;
}
}
Session Fixation攻击:
解决:
@GetMapping("/login")
public void login(@Validated UserInfo user,HttpServletRequest request) throws IOException{
UserInfo info = userService.login(user);
HttpSession session = request.getSession(false);
if(session != null){
session.invalidate(); //说明之前有人用过,使它失效
}
request.getSession(true).setAttribute("User",info);
}
cookie信息:
Path = / : 代表只有/ 下的请求才会携带cookie
Secure:只有在Https下才携带
HttpOnly:不能被javaScrpt发送,防止跨站攻击
见后面文章。