一,Security
认证:验证当前用户是否为本系统用户,并且要确认是哪一个用户。
授权:经过认证后判断当前用户是否有权限进行某个操作。
二,添加 Security 依赖
<!--security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.5.4</version>
</dependency>
三,配置 SecurityConfig 配置类
package com.guo.config.security;
import com.guo.config.security.exception.AccessDeniedHandlerImpl;
import com.guo.config.security.exception.AuthenticationEntryPointImpl;
import com.guo.config.security.filter.JwtAuthenticationTokenFilter;
import com.guo.config.security.handler.LoginFailHandler;
import com.guo.config.security.handler.MyLoginSuccessHandler;
import com.guo.config.security.handler.MyLogoutSuccessHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
//开启 注解使用功能
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyLoginSuccessHandler loginSuccessHandler;
@Autowired
private LoginFailHandler failHandler;
@Autowired
private MyLogoutSuccessHandler logoutSuccessHandler;
@Autowired
private JwtAuthenticationTokenFilter tokenFilter;
@Autowired
private AuthenticationEntryPointImpl authenticationEntryPoint;
@Autowired
private AccessDeniedHandlerImpl accessDeniedHandler;
/**
* 创建 BCryptPasswordEncoder 注入容器中
* @return
*/
@Bean
public PasswordEncoder getPasswordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 获取 认证的 bean 注入spring容器
* @return
* @throws Exception
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* 一般用这个 http
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 前后端分离 一般关闭跨域token 一般用不到
.csrf().disable()
//不通过Session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
//对于登录接口允许 匿名访问
// anonymous 表示匿名访问(表示未登录状态即可访问)
// permitAll 表示 登录状态或未登录状态都可以访问 前后端不分离需要配置好多这种路径
.antMatchers("/user/login").anonymous()
// 放行申请授权路径 /test/hello测试过滤拦截
.antMatchers("/oauth/**","/test/hello").permitAll()
//下边的是 基于配置类的权限配置
// .antMatchers("/test/hello").hasAuthority("admin")
//除上边的所有请求都需要经过鉴权认证
.anyRequest().authenticated();
// 之前定义的过滤器只是放在 spring 容器当中 security 需要使用就需要进行如下配置
//参数一:表示自定义的过滤器 参数二 : 字节码对象 表示需要把自定义过滤器放在哪一个过滤器之前
http.addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class);
//配置异常处理器
http.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler);
//允许跨域
// http.cors();
//开启表单
// http.formLogin().permitAll();
}
// /**
// * 授权码模式测试
// * @param http
// * @throws Exception
// */
// @Override
// protected void configure(HttpSecurity http) throws Exception {
// http.cors().disable()//禁用跨域
// .authorizeRequests()//配置权限
// .antMatchers("/oauth/**").permitAll()//oauth接口全部允许访问
// .anyRequest().authenticated()//其他接口需要认证
// .and()
// .formLogin().permitAll();//放行登录接口(表单)
// }
// /**
// * 配置登陆成功处理器 配置登录失败处理器 配置登出 注销成功处理器
// * 进行别的可以将 此注释 否则会产生那个冲突
// * @param http
// * @throws Exception
// */
// @Override
// protected void configure(HttpSecurity http) throws Exception {
// //调用父类的方法
super.configure(http);
// http.formLogin()
// //配置登陆成功处理器
// .successHandler(loginSuccessHandler)
// //配置登录失败处理器
// .failureHandler(failHandler)
// .and()
// //配置登出 注销成功处理器
// .logout()
// .logoutSuccessHandler(logoutSuccessHandler)
// .and()
// //配置认证 下述表示所有接口都需要认证
// .authorizeRequests()
// //授权码测试 放行路径
.antMatchers("/oauth/**").anonymous()
// .anyRequest()
// .authenticated();
//
// }
}
四,添加 JwtToken过滤器
package com.guo.config.security.filter;
import com.guo.config.security.entity.LoginUser;
import com.guo.config.security.jwt.SanGengJwtUtils;
import com.guo.exception.MsgException;
import com.guo.exception.MyException;
import com.guo.utils.StringUtils;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
// TODO: 2022/8/25 项目中两个过滤器怎样实现顺序 一个Filter一个OncePerRequestFilter
@Component
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private RedisTemplate redisTemplate;
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
log.info("OncePerRequestFilter 执行");
// filterChain.doFilter(httpServletRequest,httpServletResponse);
String requestURI = httpServletRequest.getRequestURI();
//获取 token
//此处表示 未携带 token 直接走人
String token = httpServletRequest.getHeader("token");
if (StringUtils.isEmpty(token)) {
//放行 方便后续过滤器的执行
filterChain.doFilter(httpServletRequest, httpServletResponse);
//return 的作用是 过滤器链会执行两次 如果不return的话 响应过滤器链时会再次执行 解析 token
return;
}
//解析 token
Claims claims = null;
String userId;
try {
claims = SanGengJwtUtils.parseJWt(token);
userId = claims.getSubject();
} catch (Exception e) {
e.printStackTrace();
throw new MyException(MsgException.TOKEN_PARSE_EXCEPTION);
}
//从 redis 中获取用户信息
String redisKey = "token:" + userId;
LoginUser loginUser = (LoginUser) redisTemplate.opsForValue().get(redisKey);
if (loginUser == null) {
//可能表示用户登录过期 啥的 此处表示有 token 但是 未查到用户信息 redis里边没有
// TODO: 2022/8/25 token这里逻辑有点迷
// TODO: 2022/8/25 还有为什么这里抛出自定义异常 自己的异常没有转接到异常处理
throw new MyException(MsgException.TOKEN_PARSE_EXCEPTIONS);
}
//存入 SecurityContextHolder 中 之后就有权限访问其他接口
//三个参数 表示用户已经处于认证状态 之后访问其他接口就不需要在去认证
// 参数一 用户信息 参数二 :不知道 null 参数三 :用户所具有的权限 现在没有可以先设置为 null
// TODO: 2022/8/25 随后获取权限信息封装传入 authorites
// UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, null);
// TODO: 2022/8/26 权限信息已写入
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getList());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
//放行 方便后续过滤器的执行
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
五:UserDetails 的实现类 LoginUser
package com.guo.config.security.entity;
import com.alibaba.fastjson.annotation.JSONField;
import com.guo.entity.Admin;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
@Data
@NoArgsConstructor
public class LoginUser implements UserDetails {
private Admin admin;
//查询封装的权限集合
private List<String> authorityList;
// 认证需要的权限集合
// 注解表示 此属性不会被序列化
//在登录的时候会把 LoginUser 存入redis 当中 所以添加此注解
// @JSONField(serialize = false)
private List<SimpleGrantedAuthority> list;
public LoginUser(Admin admin, List<String> authorityList) {
this.admin = admin;
this.authorityList = authorityList;
}
// /**
// *
// * 老版本
// * 用于返回权限信息
// * @return
// */
// @Override
// public Collection<? extends GrantedAuthority> getAuthorities() {
// //security查询权限信息 调用此方法 需要将自己的权限信息写入
// //GrantedAuthority 实现类
// //将 authorityList 封装的string 权限信息 封装成SimpleGrantedAuthority 对象
List<GrantedAuthority> list = new ArrayList<>();
for (String authority : authorityList) {
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(authority);
list.add(simpleGrantedAuthority);
}
//
// //java 8新特性
// List<SimpleGrantedAuthority> list = authorityList.stream()
// .map(SimpleGrantedAuthority::new)
// .collect(Collectors.toList());
//
// return list;
// }
/**
* 优化
* 用于返回权限信息
* @return
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
//security查询权限信息 调用此方法 需要将自己的权限信息写入
//GrantedAuthority 实现类
//将 authorityList 封装的string 权限信息 封装成SimpleGrantedAuthority 对象
if (list != null) {
return list;
}
//java 8新特性
list = authorityList.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
return list;
}
/**
* 获取用户认证密码
* @return
*/
@Override
public String getPassword() {
return admin.getPassword();
}
/**
* 获取用户认证用户名
* @return
*/
@Override
public String getUsername() {
return admin.getName();
}
/**
* 是否没过期
* @return
*/
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
/**
* 用户是否可用
* @return
*/
@Override
public boolean isEnabled() {
return true;
}
}
六:UserDetailsService 的实现类 UserDetailsServiceImpl
package com.guo.config.security.service.impl;
import com.guo.config.security.entity.LoginUser;
import com.guo.entity.Admin;
import com.guo.exception.MsgException;
import com.guo.exception.MyException;
import com.guo.service.AdminService;
import org.springframework.beans.factory.annotation.Autowired;
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.Service;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private AdminService adminService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{
//查询用户信息
Admin admin = adminService.selectByName(username);
//自己的理解 此处暂时没有拿到用户密码 但是可以从数据库中拿到用户信息
//可以在此处校验 用户名不存在,用户被冻结 抛出此等异常
if (admin == null) {
System.out.println("查询不到用户");
throw new MyException(MsgException.USER_NAME_PASSWORD_EXCEPTION);
// throw new UsernameNotFoundException("用户名密码错误");
}
// TODO: 2022/8/24 查询权限信息 用户里边放入权限信息 返回给登录接口
//先将权限信息写死 后续处理
List<String> authorityList = new ArrayList<>(Arrays.asList("test ","admin"));
//此处都是数据库账号密码都正确的情况下
//封装成 UserDetail对象返回 用于authenticationManager.authenticate 获取
return new LoginUser(admin,authorityList);
}
}
七,LoginService 接口
package com.guo.config.security.service;
import com.guo.entity.Admin;
import com.guo.utils.Result;
public interface LoginService {
Result login(Admin admin);
Result logout();
}
八:LoginService 接口实现类 LoginServiceImpl
package com.guo.config.security.service.impl;
import com.guo.config.security.jwt.SanGengJwtUtils;
import com.guo.config.security.entity.LoginUser;
import com.guo.config.security.service.LoginService;
import com.guo.entity.Admin;
import com.guo.exception.MsgException;
import com.guo.exception.MyException;
import com.guo.utils.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Service
public class LoginServiceImpl implements LoginService {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private RedisTemplate redisTemplate;
@Override
public Result login(Admin admin) {
// authenticationManager.authenticate() 进行用户认证
// 将 用户名和密码 封装成 AuthenticationManager 接口类型进行认证
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(admin.getName(), admin.getPassword());
// authenticationManager.authenticate() 认证时会调用 UserDetailsService 的loadUserByUsername 方法
//进行认证 也就是自己写的UserDetailsService 实现类逻辑
Authentication authenticate = null;
try {
//authenticate 里边有各种属性
// 此认证方法会抛出各种异常 此方法会校验用户和密码
authenticate = authenticationManager.authenticate(authenticationToken);
//此处的异常捕捉可以放在 实现啦UserDetailsService 的实现类中
//BadCredentialsException 可以查询到用户,但是密码错误
//InternalAuthenticationServiceException 查询不到用户,没有该用户
} catch (InternalAuthenticationServiceException e) {
throw new MyException(MsgException.USER_NAME_PASSWORD_EXCEPTION);
}
//如果认证没通过 给出对应提示
//如果认证通过,使用 userId生成 jwt,jwt存入 Result 进行返回
//userId 从 authenticate 中获取
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
Integer id = loginUser.getAdmin().getId();
String jwt = SanGengJwtUtils.createJWT(id.toString());
Map<String, Object> map = new HashMap<>();
map.put("token", jwt);
//把完整的用户信息 存入redis userId作为key 60秒过期
redisTemplate.opsForValue().set("token:" + id, loginUser,2, TimeUnit.MINUTES);
return Result.buildSuccess(map , 666L);
}
@Override
public Result logout() {
//获取 SecurityContextHolder 中用户的 id
// 一个请求过来 会先通过 jet 认证过滤器 因为认证过滤器中已经设置啦 Authentication 所获取到的是同一个
// Authentication 可以强制转换为 UsernamePasswordAuthenticationToken 用于获取认证信息
UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
Integer id = loginUser.getAdmin().getId();
//删除 Redis 中的 token 值
String redisKey = "token:" + id;
redisTemplate.delete(redisKey);
return Result.buildSuccess("注销成功", 666L);
}
}
九:自定义 认证?授权?失败异常处理
WebUtils工具类:
package com.guo.config.security.utils;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class WebUtils {
public static String renderString(HttpServletResponse response, String stringValue) {
try {
response.setStatus(200);
response.setContentType("application/json");
//设置响应编码
response.setCharacterEncoding("utf-8");
response.getWriter().println(stringValue);
} catch (IOException e) {
e.printStackTrace();
}
//可以不需要返回值
return null;
}
}
授权异常处理:
package com.guo.config.security.exception;
import com.guo.config.security.utils.WebUtils;
import com.guo.utils.JsonUtils;
import com.guo.utils.Result;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* security 自己的全局异常处理
* 鉴权异常处理
*/
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
Result result = new Result(HttpStatus.METHOD_NOT_ALLOWED.toString(), "用户鉴权失败-security的异常处理", null, 1L);
String json = JsonUtils.getJson(result);
WebUtils.renderString(httpServletResponse,json);
}
}
认证异常处理:
package com.guo.config.security.exception;
import com.guo.config.security.utils.WebUtils;
import com.guo.utils.JsonUtils;
import com.guo.utils.Result;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* security 自己的全局异常处理
* 认证异常处理
*/
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
//HttpStatus 里边封装啦各种状态码的信息
Result result = new Result(HttpStatus.UNAUTHORIZED.toString(), "用户认证失败-security的异常处理", null, 1L);
//自己写的工具类 对象转字符串
String json = JsonUtils.getJson(result);
WebUtils.renderString(httpServletResponse, json);
}
}
十:自定义登录失败,登录成功,登出成功处理
登录失败处理:
package com.guo.config.security.handler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Slf4j
@Component //可以实现接口AuthenticationFailureHandler 也可以继承此接口的实现类
public class LoginFailHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
log.info("认证失败------------登录失败");
}
}
登录成功处理:
package com.guo.config.security.handler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Slf4j
@Component //可以实现接口 AuthenticationSuccessHandler 也可以继承接口的实现类
public class MyLoginSuccessHandler implements AuthenticationSuccessHandler {
//Authentication 用户认证的对象
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
log.info("---------------认证成功");
}
}
登出成功处理:
package com.guo.config.security.handler;
import lombok.extern.slf4j.Slf4j;
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;
@Slf4j
@Component
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
log.info("-------------登出成功---------------");
}
}
十一:自定义权限校验规则
package com.guo.config.security;
import com.guo.config.security.entity.LoginUser;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import java.util.List;
@Component("my")
public class MyAuthority {
public boolean hasAuthority(String authority) {
//获取当前用户的权限
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
//loginUser 里边直接获取权限列表 可以少一步
List<String> authorityList = loginUser.getAuthorityList();
//含有权限则返回true 否则返回false
return authorityList.contains(authority);
}
}
十二:Controller 层
package com.guo.config.security.controller;
import com.guo.config.security.service.LoginService;
import com.guo.entity.Admin;
import com.guo.utils.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
@Controller
@RequestMapping("/user")
public class MyLoginController {
@Autowired
private LoginService loginService;
@PostMapping("/login")
@ResponseBody
public Result login(@RequestBody Admin admin) {
// TODO: 2022/8/25 测试一下不适用RequestBody注解 可以获取数据不能
//登录
Result login = loginService.login(admin);
return login;
}
/**
* 退出登录
* */
@GetMapping("/logout")
@ResponseBody
public Result logout() {
return loginService.logout();
}
@GetMapping("/hello")
//自定义的 权限校验规则
// @PreAuthorize("@my.hasAuthority('test')")
// 此注解需要一个 String类型的参数
// 此参数为一个方法 其实扫描后就是一个方法的调用 方法参数为String类型 使用单引号
// 固定格式
@PreAuthorize("hasAuthority('test')")
@ResponseBody
public String getHello() {
return "test hello";
}
}
十三:里边都有注释
controller->service.login->~~authentication->loadByUsername->tokenFilter
1万+

被折叠的 条评论
为什么被折叠?



