1.认证授权过程
1.使用用户名和密码进行登录操作。
2.后端接受请求进入层层Filter过滤器,对登录操作放行,放行后开始认证流程。
3.Spring Security拿到用户名和密码,将其封装成一个实现了Authentication接口的UsernamePasswordAuthenticationToken。
4.使用认证管理器把上一步产生的TOKEN对象进行登录认证。也就是authenticationManager.authenticate(authenticationToken)
5.认证管理器AuthenticationManager认证
认证成功:返回一个封装了用户角色权限等信息的Authentication对象。
认证失败:抛出AuthenticationException异常,异常将被捕获交给AuthenticationEntryPoint 处理,处理后返回给前端页面引导重新登陆。
6.SecurityContextHolder.getContext().setAuthentication(…) ,把认证管理器返回的Authentication 对象赋予给当前的 SecurityContext上下文中。
7.认证成功后访问受保护的资源时,会使用保存在SecurityContext中的Authentication 对象进行相关的权限鉴定 ,若鉴定失败抛出AccessDeniedException,异常将被ExceptionTranslationFilter处理 ,然后返回状态码403表示没有权限。
2.举个栗子
配置项
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityInDBConfig extends WebSecurityConfigurerAdapter {
@Autowired
private AuthenticationEntryPoint authenticationEntryPoint;
@Autowired
private AccessDeniedHandler accessDeniedHandler;
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
//密码加密器
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
//基于数据库认证&授权
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable() //关闭csrf
.cors() //解决跨域问题
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) //前后端分离,禁用session
.and()
//请求认证配置
.authorizeRequests()
.mvcMatchers("/system/login")
.permitAll()
.anyRequest().authenticated();
//认证授权过滤器
http.addFilterBefore(jwtAuthenticationTokenFilter , UsernamePasswordAuthenticationFilter.class);
//异常处理器
http.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint) //401异常 认证失败
.accessDeniedHandler(accessDeniedHandler); //403异常 授权失败
}
//认证管理器
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
SpringSecurity默认是前后端不分离的方式,因此前后端分离项目需要禁言Session。然后排除登录的请求接口。由于在认证前需要验证Token,因此添加一个Token过滤器来进行Token验证。
然后还需要指定401和403的失败情况的处理器,401异常进入AuthenticationEntryPoint进行处理,403鉴权失败异常进入AccessDeniedHandler进行处理,因此我们只需手动声明这两个Bean对象到IOC容器中,发生异常时就会自动进入到相应Bean中进行处理。
代码如下:
/**
* 针对于Security的异常401 403统一做异常处理器
*/
@Configuration
public class SecurityHandler {
//认证失败
@Bean
public AuthenticationEntryPoint authenticationEntryPoint(){
return new AuthenticationEntryPoint() {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException){
//使用封装好的工具把字符串响应给客户端
WebUtils.renderString(response , JSONUtil.toJsonStr(Result.error("401 认证失败,请先登陆")),401);
}
};
}
//授权失败
@Bean
public AccessDeniedHandler accessDeniedHandler(){
return new AccessDeniedHandler() {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) {
WebUtils.renderString(response, JSONUtil.toJsonStr(Result.error("403 授权失败,没有权限访问")),403);
}
};
}
}
响应工具
/**
* 将字符串渲染到客户端,向响应之中写入中聚类
*/
public class WebUtils {
/**
* 将字符串渲染到客户端
* @param response 渲染对象
* @param data 待渲染的字符串
*/
public static String renderString(HttpServletResponse response, String data,Integer code) {
try {
response.setStatus(code);
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.getWriter().print(data);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
UserDetails
/**
* @description 存储worker的角色以及多个权限
*/
@NoArgsConstructor
@Data
public class LoginUser implements UserDetails {
private Worker worker;
public LoginUser(Worker worker) {
this.worker = worker;
}
//已授予的权限
private List<SimpleGrantedAuthority> authorities;
//获取当前认证账户的角色和权限信息
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
if (!CollectionUtil.isEmpty(authorities)) {
return authorities;
}
authorities = new ArrayList<>();
Role role = worker.getRole();
authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName()));
Set<Permission> permissions = role.getPermissions();
if(role.getPermissions()!=null){
permissions.stream().forEach(permission -> {
authorities.add(new SimpleGrantedAuthority(permission.getPermissionName()));
});
}
return authorities;
}
@Override
public String getPassword() {
return worker.getPassword();
}
@Override
public String getUsername() {
return worker.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
这里的Worker对象就是数据库中对应包含角色和权限信息的实体类,一个用户可以有多个角色,这里我直接就写了一个角色,一个角色对应多个权限。注意角色的唯一标识需要以ROLE_开头。下方可根据需求设置,这里统一返回true。这些判断会在认证过程中依次执行判断,若返回false则认证失败。
Token过滤器Filter
/**
* * 解析请求头中的token,并验证合法性。
* * 继承 OncePerRequestFilter 保证请求经过过滤器一次
*/
@Component
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private JwtProperties jwtProperties;
@Autowired
private RedisUtil redisUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String url = request.getRequestURI();
if (url.equals("/system/login")) {
//放行
filterChain.doFilter(request, response);
return;
}
//1.从请求头中获取token数据
String token = request.getHeader(jwtProperties.getTokenName());
//2.验证token合法性
log.info("验证token合法性:{}", token);
Long id = null;
try {
Map<String, Object> map = JwtUtil.parseToken(jwtProperties.getSecretKey(), token);
//直接object转Long会报错
id = Long.parseLong(map.get("id").toString());
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Token已过期,请重新登陆");
}
//从redis中获取用户信息
log.info("当前用户id:{}", id);
ThreadLocalUtil.setCurrentId(id);
Object worker = redisUtil.get(RedisKeyConstant.WORKER_PREFIX + "_" + id);
if(ObjectUtil.isNull(worker)){
throw new AccountNotFoundException("缓存中用户不存在,请重新登录");
}
Worker w = (Worker)worker;
LoginUser loginUser = new LoginUser(w);
//4.将Authentication对象(用户信息、已认证状态、权限信息)存入 SecurityContextHolder
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser , null , loginUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
//放行
filterChain.doFilter(request, response);
}
}
UserDetails登录业务
@Service
@Slf4j
public class SecurityUserDetailService implements UserDetailsService {
@Autowired
private WorkerMapper workerMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if (StringUtils.isEmpty(username)) {
throw new RuntimeException("用户名不存在");
}
Worker worker = workerMapper.selectAll(phone);
if (ObjectUtils.isEmpty(worker)) {
throw new RuntimeException("账号不存在");
}
LoginUser loginUser = new LoginUser(worker);
return loginUser;
}
}
这里是查询数据库获取用户信息的地方,通过Mapper编写SQL查询用户的角色和权限信息,用户和角色是一对多(我这里写的是一对一),角色和权限也是一对多。
用户登录业务逻辑
public WorkerLoginVO login(String username,String password) {
//密码登录
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username,password);
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
LoginUser loginUser = (LoginUser)authenticate.getPrincipal();
Worker worker = loginUser.getWorker();
//Redis缓存权限信息
redisUtil.set(RedisKeyConstant.WORKER_PREFIX + "_" + worker.getId(), worker, 6 * 60 * 60L);
//生成token,下面是自己的其他业务
HashMap<String, Object> claims = new HashMap<>();
claims.put("id", worker.getId());
String token = JwtUtil.genToken(jwtProperties.getSecretKey(), jwtProperties.getTtl(), claims);
WorkerLoginVO workerLoginVO = WorkerLoginVO.builder().token(token).roleId(worker.getRoleId()).build();
return workerLoginVO;
}
认证授权的实现逻辑有多种方式,此处仅展示整个认证的过程,应根据自己业务需求来合理制定。
标签:return,Spring,worker,认证,response,new,Security,public From: https://blog.csdn.net/qq_52464274/article/details/140673612