目录

一、依赖导入

二、添加自定义UserDetail

三、服务层

四、Security配置

五、登录登出的服务层

六、登录参数

七、Security获取登录的User对象

八、控制层


一、依赖导入

<!--springSecurity-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

二、添加自定义UserDetail

添加实体类: 数据库表:

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * author: Dragon Wu
 * date: 2022-07-19 12:53
 */
 
/**
    * 系统人员
    */
@ApiModel(value="系统人员")
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "sys_user")
public class SysUser implements Serializable {
    /**
     * 主键
     */
    @TableId(value = "id")
    @ApiModelProperty(value="主键")
    private Long id;

    /**
     * 权限组id
     */
    @TableField(value = "jurisdiction_id")
    @ApiModelProperty(value="权限组id")
    private Long jurisdictionId;

    /**
     * 用户名
     */
    @TableField(value = "username")
    @ApiModelProperty(value="用户名")
    private String username;

    /**
     * 昵称
     */
    @TableField(value = "nickname")
    @ApiModelProperty(value="昵称")
    private String nickname;

    /**
     * 密码
     */
    @TableField(value = "password")
    @ApiModelProperty(value="密码")
    private String password;

    /**
     * 头像
     */
    @TableField(value = "avatar")
    @ApiModelProperty(value="头像")
    private String avatar;

    /**
     * 性别: 0女,1男
     */
    @TableField(value = "sex")
    @ApiModelProperty(value="性别: 0女,1男")
    private Boolean sex;

    /**
     * 手机号
     */
    @TableField(value = "mobile")
    @ApiModelProperty(value="手机号")
    private String mobile;

    /**
     * 邮箱
     */
    @TableField(value = "email")
    @ApiModelProperty(value="邮箱")
    private String email;

    /**
     * 账号状态:0停用,1正常
     */
    @TableField(value = "stat")
    @ApiModelProperty(value="账号状态:0停用,1正常")
    private Integer stat;

    /**
     * 是否删除:0未删除,1已删除
     */
    @TableField(value = "is_del")
    @ApiModelProperty(value="是否删除:0未删除,1已删除")
    private Boolean isDel;

    /**
     * 创建人id
     */
    @TableField(value = "create_by")
    @ApiModelProperty(value="创建人id")
    private Long createBy;

    /**
     * 创建时间
     */
    @TableField(value = "create_time")
    @ApiModelProperty(value="创建时间")
    private LocalDateTime createTime;

    /**
     * 更新人id
     */
    @TableField(value = "update_by")
    @ApiModelProperty(value="更新人id")
    private Long updateBy;

    /**
     * 更新时间
     */
    @TableField(value = "update_time")
    @ApiModelProperty(value="更新时间")
    private LocalDateTime updateTime;

    private static final long serialVersionUID = 1L;

    public static final String COL_ID = "id";

    public static final String COL_JURISDICTION_ID = "jurisdiction_id";

    public static final String COL_USERNAME = "username";

    public static final String COL_NICKNAME = "nickname";

    public static final String COL_PASSWORD = "password";

    public static final String COL_AVATAR = "avatar";

    public static final String COL_SEX = "sex";

    public static final String COL_MOBILE = "mobile";

    public static final String COL_EMAIL = "email";

    public static final String COL_STAT = "stat";

    public static final String COL_IS_DEL = "is_del";

    public static final String COL_CREATE_BY = "create_by";

    public static final String COL_CREATE_TIME = "create_time";

    public static final String COL_UPDATE_BY = "update_by";

    public static final String COL_UPDATE_TIME = "update_time";
}
import com.alibaba.fastjson.annotation.JSONField;
import com.party.domain.SysUser;
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.Collection;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * author: Dragon Wu
 * date: 2022-07-10 13:39
 */
@Data
@NoArgsConstructor
public class LoginUser implements UserDetails {

    private SysUser sysUser;

    //用户权限
    private List<String> jurisdiction;

    //将不会被序列化到redis里
    @JSONField(serialize = false)
    private List<SimpleGrantedAuthority> authorities;

    public LoginUser(SysUser sysUser,List<String> jurisdiction){
        this.sysUser=sysUser;
        this.jurisdiction=jurisdiction;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        //把jurisdiction里的权限信息字符串封装成SimpleGrantedAuthority对象
        if(!Objects.isNull(authorities)){
            return authorities;
        }
        authorities=jurisdiction.stream()
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
        return authorities;
    }

    @Override
    public String getPassword() {
        return sysUser.getPassword();
    }

    @Override
    public String getUsername() {
        return sysUser.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;
    }
}

三、服务层

import com.baomidou.mybatisplus.extension.service.IService;
import com.party.domain.SysUser;
import com.party.model.param.SysUserParam;
import com.party.vo.PageVO;

import java.util.List;

/**
 * author: Dragon Wu
 * date: 2022-07-19 12:53
 */
 
public interface SysUserService extends IService<SysUser>{

    //密码正则匹配
    public static final String PW_PATTERN = "^(?![A-Za-z0-9]+$)(?![a-z0-9\\W]+$)(?![A-Za-z\\W]+$)(?![A-Z0-9\\W]+$)[a-zA-Z0-9\\W]{8,}$";

    //邮箱正则匹配
    public static final String EMAIL_MATCHER="[a-zA-Z0-9]+@[a-zA-Z0-9]+\\.[a-zA-Z0-9]+";

    /**
     * 添加系统人员
     * @param sysUser 超级管理员
     * @param sysUserParam 人员参数
     * @return 成功true
     * @throws RuntimeException 异常
     */
    Boolean insert(SysUser sysUser, SysUserParam sysUserParam) throws RuntimeException;

    /**
     * 删除人员
     * @param sysUser 超级管理员
     * @param id 用户id
     * @return  成功true
     */
    Boolean deleteItem(SysUser sysUser,Long id);

    /**
     * 更新人员
     * @param sysUser 超级管理员
     * @param sysUserParam 人员参数
     * @return 成功true
     */
    Boolean updateItem(SysUser sysUser,SysUserParam sysUserParam);

    /**
     * 分页查询
     * @param sysUser 超级管理员
     * @param currentPage 当前页码
     * @param pageSize 页面大小
     * @return PageVO
     */
    PageVO<List<SysUser>> queryPage(SysUser sysUser, Integer currentPage, Integer pageSize);

    /**
     * 修改人员状态
     * @param sysUser 超级管理员
     * @param id 禁用人员id
     * @param status 账号状态:0停用,1正常
     * @return 成功true
     */
    Boolean changeStatus(SysUser sysUser,Long id,Integer status);

}

服务实现:

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.party.constant.Constants;
import com.party.domain.SysUser;
import com.party.mapper.SysUserMapper;
import com.party.model.LoginUser;
import com.party.service.JurisdictionConnectService;
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.List;
import java.util.Objects;

/**
 * author: Dragon Wu
 * date: 2022-07-10 13:30
 */
@Service
public class UserDetailServiceImpl implements UserDetailsService {
    @Autowired
    private SysUserMapper sysUserMapper;

    @Autowired
    private JurisdictionConnectService jurisdictionConnectService;

    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
        //查询用户信息
        LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SysUser::getIsDel, false)
                .eq(SysUser::getStat, Constants.ONE).eq(SysUser::getUsername, userName);
        SysUser user = sysUserMapper.selectOne(queryWrapper);
        //如果没有查询到用户就抛出异常
        if (Objects.isNull(user)) {
            throw new UsernameNotFoundException("用户名不存在!");
        }
        //查询对应的权限信息
//        List<String> jurisdiction=new ArrayList<>(Arrays.asList("test","admin"));
        List<String> jurisdiction = jurisdictionConnectService.queryJurisdictionByGroupId(user.getJurisdictionId());
        //将数据封装到UserDetail返回
        return new LoginUser(user, jurisdiction);
    }

}

通过这个服务sysUser对象和查询的权限信息会被一起封装成一个UserDetail自定义对象。

四、Security配置

import com.party.filter.JwtAuthenticationTokenFilter;
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.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * author: Dragon Wu
 * date: 2022-07-10 13:49
 */
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    @Autowired
    private AuthenticationEntryPoint authenticationEntryPoint;

    @Autowired
    private AccessDeniedHandler accessDeniedHandler;

    //允许匿名访问的
    private static final String[] ALLOW_ASK={
            "/sys/login",
            "/captcha"
    };

    //总数允许访问的
    private static final String[] ALWAYS_ALLOW_ASK={
            "/v2/api-docs",
            "/swagger-resources/configuration/ui",//用来获取支持的动作
            "/swagger-resources",//用来获取api-docs的URI
            "/swagger-resources/configuration/security",//安全选项
            "/webjars/**",
            "/swagger-ui.html",//以上为api文档接口访问路径
            "/**/website/**",//拥有该路径的就一直允许访问
    };

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //关闭csrf
                .csrf().disable()
                //不通过Session获取SecurityContext
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                //对应登录接口允许匿名访问
                .antMatchers(ALWAYS_ALLOW_ASK).permitAll()
                .antMatchers(ALLOW_ASK).anonymous()
                //除上面接口全都需要鉴权访问
                .anyRequest().authenticated();

        //添加过滤器
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

        //添加异常处理器
        http.exceptionHandling()
                //认证失败的处理器
                .authenticationEntryPoint(authenticationEntryPoint)
                //授权失败处理器
                .accessDeniedHandler(accessDeniedHandler);

        //允许跨域
        http.cors();
    }

    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

}

五、登录登出的服务层

import com.party.constant.Constants;
import com.party.domain.SysUser;
import com.party.model.LoginParam;
import com.party.model.LoginUser;
import com.party.service.EasyCaptchaService;
import com.party.service.LoginService;
import com.party.util.JwtUtils;
import com.party.util.RedisUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
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.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * author: Dragon Wu
 * date: 2022-07-10 17:43
 */
@Service
public class LoginServiceImpl implements LoginService {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private RedisUtils redisUtils;

    @Autowired
    private EasyCaptchaService easyCaptchaService;


    @Override
    public String login(LoginParam loginParam) throws RuntimeException{
        //判断验证码是否正确
        if(!easyCaptchaService.verifyCaptchaCode(loginParam.getUuid(),loginParam.getCode())){
            throw new RuntimeException("验证码错误");
        }
        //AuthenticationManager authenticate进行用户认证
        UsernamePasswordAuthenticationToken authenticationToken=new UsernamePasswordAuthenticationToken(loginParam.getUsername(),loginParam.getPassword());
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);
        //如果认证没通过给出相应的提示
        if(Objects.isNull(authenticate)){
            throw new RuntimeException(Constants.LOGIN_FAIL);
        }
        //如果认证通过了,使用userId生成JWT
        LoginUser loginUser= (LoginUser) authenticate.getPrincipal();
        String userId = loginUser.getSysUser().getId().toString();
        Map<String,Object> map=new HashMap<>();
        map.put(Constants.USER_ID,userId);
        String jwt= JwtUtils.createJWT(map);
        //判断用户是否已经登录
        Object object=redisUtils.getCacheObject(Constants.LOGIN_PRE+userId);
        if(!Objects.isNull(object)){
            throw new RuntimeException("用户已经登录");
        }
        //把完整的用户信息存入redis userId作为key, map为value
        redisUtils.setCacheObject(Constants.LOGIN_PRE+userId,loginUser,Constants.LOGIN_TIME, TimeUnit.HOURS);
        return jwt;
    }

    @Override
    public Boolean logout() {
        //获取SecurityContextHolder里的用户id
        UsernamePasswordAuthenticationToken authentication =
                (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
        SysUser sysUser=(SysUser) authentication.getPrincipal();
        Long id=sysUser.getId();
        //删除redis中的值
        redisUtils.deleteObject(Constants.LOGIN_PRE+id);
        return true;
    }

    @Override
    public Map<String,String> getCaptcha() throws IOException {
        return easyCaptchaService.outputCaptchaImg(EasyCaptchaService.CAPTCHA_TYPE_GIF);
    }
}

六、登录参数

import com.party.constant.Constants;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.hibernate.validator.constraints.Length;

import javax.validation.constraints.NotBlank;

/**
 * author: Dragon Wu
 * date: 2022-07-12 11:19
 */
@Data
@ApiModel(value = "登录参数")
public class LoginParam {

    @NotBlank
    @ApiModelProperty(value = "用户名")
    private String username;

    @NotBlank
    @ApiModelProperty(value = "密码")
    private String password;

    @NotBlank
    @Length(min= Constants.CODE_LEN,max = Constants.CODE_LEN)
    @ApiModelProperty(value = "验证码")
    private String code;

    @NotBlank
    @ApiModelProperty(value = "获取验证码的uuid")
    private String uuid;
}

七、Security获取登录的User对象

import com.party.domain.SysUser;
import org.springframework.security.core.context.SecurityContextHolder;

/**
 * author: Dragon Wu
 * date: 2022-07-12 15:30
 */
public class SecurityUtils {

    public static SysUser getUser(){
        return (SysUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    }
}

八、控制层

package com.party.controller;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.exceptions.ApiException;
import com.party.constant.Constants;
import com.party.model.LoginParam;
import com.party.model.R;
import com.party.service.LoginService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;
import java.util.Map;

/**
 * author: Dragon Wu
 * date: 2022-07-10 17:34
 */
@Api(tags = "后台登录")
@RestController
@Slf4j
public class LoginController {

    @Autowired
    private LoginService loginService;

    @PostMapping("/sys/login")
    @ApiOperation(value = "后台登录")
    public R<String> login(@Valid @RequestBody LoginParam loginParam) {
        try {
            return R.ok(loginService.login(loginParam), "登录成功");
        } catch (ApiException e2) {
            return R.fail(null, e2.getMessage());
        } catch (Exception e) {
            log.error("后台登录出现异常:" + JSONObject.toJSONString(loginParam), e);
            return R.fail(null, Constants.LOGIN_FAIL);
        }
    }

    @GetMapping("/sys/logout")
    @ApiOperation(value = "退出登录")
    public R<String> logout() {
        try {
            return loginService.logout() ? R.ok(null, Constants.LOGOUT_OK) : R.fail(null, Constants.LOGOUT_FAIL);
        } catch (Exception e) {
            log.error("退出登录出现异常:" + e);
            return R.fail(null, Constants.LOGOUT_FAIL);
        }
    }

    @ApiOperation(value = "获取验证码")
    @GetMapping("/captcha")
    public R<Map<String, String>> captcha() {
        try {
            return R.ok(loginService.getCaptcha(), Constants.GET_SOURCE_OK);
        } catch (Exception e) {
            log.error("获取验证码出现异常:", e);
            return R.fail(null, Constants.GET_SOURCE_FAIL);
        }
    }

}

登录实现总结完毕