Springboot整合SpringSecurity

一.Spring Security介绍

Spring Security是基于Spring生态圈的,用于提供安全访问控制解决方案的框架。Spring Security的安 全管理有两个重要概念,分别是Authentication(认证)和Authorization(授权)。 为了方便Spring Boot项目的安全管理,Spring Boot对Spring Security安全框架进行了整合支持,并提 供了通用的自动化配置,从而实现了Spring Security安全框架中包含的多数安全管理功能。

Spring Security登录认证主要涉及两个重要的接口 UserDetailService和UserDetails接口。

UserDetailService接口主要定义了一个方法 loadUserByUsername(String username)用于完成用户信息的查 询,其中username就是登录时的登录名称,登录认证时,需要自定义一个实现类实现UserDetailService接 口,完成数据库查询,该接口返回UserDetail。

UserDetail主要用于封装认证成功时的用户信息,即UserDetailService返回的用户信息,可以用Spring

自己的User对象,但是最好是实现UserDetail接口,自定义用户对象。

二.Spring Security认证步骤

1. 自定UserDetails类:当实体对象字段不满足时需要自定义UserDetails,一般都要自定义

UserDetails。

2. 自定义UserDetailsService类,主要用于从数据库查询用户信息。

3. 创建登录认证成功处理器,认证成功后需要返回JSON数据,菜单权限等。

4. 创建登录认证失败处理器,认证失败需要返回JSON数据,给前端判断。

5. 创建匿名用户访问无权限资源时处理器,匿名用户访问时,需要提示JSON。

6. 创建认证过的用户访问无权限资源时的处理器,无权限访问时,需要提示JSON。

7. 配置Spring Security配置类,把上面自定义的处理器交给Spring Security。

三.Spring Security认证实现

3.1添加Spring Security依赖

在pom.xml文件中添加Spring Security核心依赖,代码如下所



    org.springframework.boot

    spring-boot-starter-security

  3.2自定义UserDetails

   当实体对象字段不满足时Spring Security认证时,需要自定义UserDetails。

   1. 将User类实现UserDetails接口

   2. 将原有的isAccountNonExpired、isAccountNonLocked、isCredentialsNonExpired和isEnabled属性修 改成boolean类型,同时添加authorities属性。

@Data
@TableName("sys_user")

public class User implements Serializable, UserDetails {
    
    //省略原有的属性......

    
    /**

     * 帐户是否过期(1 未过期,0已过期)

     */

    private boolean isAccountNonExpired = true;
    /**

     * 帐户是否被锁定(1 未过期,0已过期)

     */

    private boolean isAccountNonLocked = true;
    /**

     * 密码是否过期(1 未过期,0已过期)

     */

    private boolean isCredentialsNonExpired = true;
    /**

     * 帐户是否可用(1 可用,0 删除用户)

     */

    private boolean isEnabled = true;
    /**

     * 权限列表

     */

    @TableField(exist = false)
    Collection authorities;

3.3.编写Service接口

 

public interface UserService extends IService {
    /**

     * 根据用户名查询用户信息

     * @param userName

     * @return

     */

    User findUserByUserName(String userName);
}

 

3.4.编写ServiceImpl

package com.manong.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.manong.entity.User;
import com.manong.dao.UserMapper;
import com.manong.service.UserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 

* 服务实现类 *

* * @author lemon * @since 2022-12-06 */ @Service @Transactional public class UserServiceImpl extends ServiceImpl implements UserService { @Override public User findUserByUserName(String username) { //创建条件构造器对象 QueryWrapper queryWrapper=new QueryWrapper(); queryWrapper.eq("username",username); //执行查询 return baseMapper.selectOne(queryWrapper); } }

3.5. 自定义UserDetailsService类

package com.manong.config.security.service;

import com.manong.entity.Permission;
import com.manong.entity.User;
import com.manong.service.PermissionService;
import com.manong.service.UserService;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
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 javax.annotation.Resource;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/*
* 用户认证处理器类
* */
@Component
public class CustomerUserDetailService implements UserDetailsService {

    @Resource
    private UserService userService;

    @Resource
    private PermissionService permissionService;


    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{
        //根据对象查找用户信息
        User user = userService.findUserByUserName(username);
        //判断对象是否为空
        if(user==null){
            throw new UsernameNotFoundException("用户的账号密码错误");
        }
        //查询当前登录用户拥有权限列表
        List permissionList = permissionService.findPermissionListByUserId(user.getId());
        //获取对应的权限编码
        List codeList = permissionList.stream()
                .filter(Objects::nonNull)
                .map(item -> item.getCode())
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        //将权限编码转换成数据
        String [] strings=codeList.toArray(new String[codeList.size()]);
        //设置权限列表
        List authorityList = AuthorityUtils.createAuthorityList(strings);
        //将权限列表设置给User
        user.setAuthorities(authorityList);
        //设置该用户拥有的菜单
        user.setPermissionList(permissionList);
        //查询成功
        return user;
    }
}

四.通常情况下,我们需要自定义四个类来获取处理类

包括成功,失败,匿名用户,登录了但没有权限的用户

4.1.成功

package com.manong.config.security.handler;


import com.alibaba.fastjson.serializer.SerializerFeature;
import com.manong.entity.User;
import com.manong.utils.JwtUtils;
import com.manong.utils.LoginResult;
import com.manong.utils.ResultCode;
import io.jsonwebtoken.Jwts;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import sun.net.www.protocol.http.AuthenticationHeader;

import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

import com.alibaba.fastjson.JSON;

/*
 * 登录认证成功处理器类
 * */
@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
    @Resource
    private JwtUtils jwtUtils;
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        //设置相应编码格式
        response.setContentType("application/json;charset-utf-8");
        //获取当前登录用户的信息
        User user = (User) authentication.getPrincipal();
        //创建token对象
        String token = jwtUtils.generateToken(user);
        //设置token的秘钥和过期时间
        long expireTime = Jwts.parser()
                .setSigningKey(jwtUtils.getSecret())
                .parseClaimsJws(token.replace("jwt_", ""))
                .getBody().getExpiration().getTime();//设置过期时间
        //创建LOgin登录对象
        LoginResult loginResult=new LoginResult(user.getId(), ResultCode.SUCCESS,token,expireTime);
        //将对象转换成json格式
        //消除循环引用
        String result = JSON.toJSONString(loginResult, SerializerFeature.DisableCircularReferenceDetect);
        //获取输出流
        ServletOutputStream outputStream = response.getOutputStream();
        outputStream.write(result.getBytes(StandardCharsets.UTF_8));
        outputStream.flush();
        outputStream.close();

    }
}

4.2 失败

package com.manong.config.security.handler;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.baomidou.mybatisplus.extension.api.R;
import com.manong.entity.User;
import com.manong.utils.Result;
import com.manong.utils.ResultCode;
import org.springframework.security.authentication.*;
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.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

@Component
public class LoginFailureHandler implements AuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        //设置相应编码格式
        response.setContentType("application/json;charset-utf-8");
        //获取输出流
        ServletOutputStream outputStream = response.getOutputStream();
        //定义变量,保存异常信息
        String message=null;
        //判断异常类型
        if(exception instanceof AccountExpiredException){
            message="账户过期失败";
        }
        else if(exception instanceof BadCredentialsException){
            message="用户名的账号密码错误,登录失败";
        }
        else if(exception instanceof CredentialsExpiredException){
            message="密码过期,登录失败";
        }
        else if(exception instanceof DisabledException){
            message="账号过期,登录失败";
        }
        else if(exception instanceof LockedException){
            message="账号被上锁,登录失败";
        }
        else if(exception instanceof InternalAuthenticationServiceException){
            message="用户不存在";
        }

        else {
            message="登录失败";
        }
        //将结果转换为Json格式
        String result = JSON.toJSONString(Result.error().code(ResultCode.ERROR).message(message));
        //将结果保存到输出中
        outputStream.write(result.getBytes(StandardCharsets.UTF_8));
        outputStream.flush();
        outputStream.close();


    }
}

 4.3 匿名无用户

package com.manong.config.security.handler;


import com.alibaba.fastjson.serializer.SerializerFeature;
import com.manong.entity.User;
import com.manong.utils.Result;
import com.manong.utils.ResultCode;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import sun.net.www.protocol.http.AuthenticationHeader;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

import com.alibaba.fastjson.JSON;

/*
 * 匿名访问无权限资源处理器
 * */
@Component
public class AnonymousAuthenticationHandler implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        response.setContentType("application/json;charset-utf-8");
        //获取输出流
        ServletOutputStream outputStream = response.getOutputStream();
        //将对象转换成json格式
        //消除循环引用
        String result = JSON.toJSONString(Result.error().code(ResultCode.NO_AUTH).message("匿名用户无权限访问"), SerializerFeature.DisableCircularReferenceDetect);
        //获取输出流
        outputStream.write(result.getBytes(StandardCharsets.UTF_8));
        outputStream.flush();
        outputStream.close();
    }
}

 4.4 登录了但是没有权限的用户

package com.manong.config.security.handler;


import com.alibaba.fastjson.serializer.SerializerFeature;
import com.manong.entity.User;
import com.manong.utils.Result;
import com.manong.utils.ResultCode;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import sun.net.www.protocol.http.AuthenticationHeader;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

import com.alibaba.fastjson.JSON;

/*
 * 认证用户访问无权限资源处理器
 * */
@Component
public class CustomerAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        response.setContentType("application/json;charset-utf-8");
        ServletOutputStream outputStream = response.getOutputStream();
        //将对象转换成json格式
        //消除循环引用
        String result = JSON.toJSONString(Result.error().code(ResultCode.NO_AUTH).message("用户无权限访问,请联系教务处"), SerializerFeature.DisableCircularReferenceDetect);
        //获取输出流
        outputStream.write(result.getBytes(StandardCharsets.UTF_8));
        outputStream.flush();
        outputStream.close();
    }
}
    

五.编写SpringSecurity配置类,把上面的四个类进行合并

package com.manong.config.security.service;

import com.manong.config.security.handler.AnonymousAuthenticationHandler;
import com.manong.config.security.handler.CustomerAccessDeniedHandler;
import com.manong.config.security.handler.LoginFailureHandler;
import com.manong.config.security.handler.LoginSuccessHandler;
import org.springframework.context.annotation.Bean;
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.configuration.EnableWebSecurity;
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.stereotype.Component;

import javax.annotation.Resource;

@Component
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private LoginSuccessHandler loginSuccessHandler;
    @Resource
    private LoginFailureHandler loginFailureHandler;
    @Resource
    private CustomerAccessDeniedHandler customerAccessDeniedHandler;
    @Resource
    private AnonymousAuthenticationHandler anonymousAuthenticationHandler;
    @Resource
    private CustomerUserDetailService customerUserDetailService;

    //注入加密类
    @Bean
    public BCryptPasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
    //处理登录认证
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //登录过程处理
        http.formLogin()    //表单登录
                .loginProcessingUrl("/api/user/login") //登录请求url地址
                .successHandler(loginSuccessHandler)   //认证成功
                .failureHandler(loginFailureHandler)   //认证失败
                .and()
                .csrf().disable()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS) //不创建Session
                .and().authorizeRequests() //设置需要拦截的请求
                .antMatchers("/api/user/login").permitAll()//登录放行
                .anyRequest().authenticated()  //其他请求一律拦截
                .and()
                .exceptionHandling()
                .authenticationEntryPoint(anonymousAuthenticationHandler)  //匿名无权限类
                .accessDeniedHandler(customerAccessDeniedHandler)       //认证用户无权限
                .and()
                .cors();//支持跨域
    }


    //认证配置处理器
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(customerUserDetailService)
                .passwordEncoder(this.passwordEncoder());//密码加密
    }
}

你可能感兴趣的:(spring,boot,spring,java)