使用SpringSecurity实现登录及鉴权

介绍

前一段时间公司要求登录和鉴权使用SpringSecurity,看了很多都感觉不太适合企业开发,于是自己整理了一下,基于@PreAuthorize注解鉴权,大大的方便的权限控制。

设计技术

本项目使用mysql数据库、mybatis-plus、redis缓存、jwt、hutool工具包、fastjson2。

Maven

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

<!--        数据库相关-->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.3</version>
        </dependency>

<!--        lombook-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

<!--        hutool-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.15</version>
        </dependency>

        <!--redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>2.6.13</version>
        </dependency>

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

        <!--jwt依赖-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>

        <!--JSON解析工具-->
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>2.0.10</version>
        </dependency>

数据库

需要四张表,如下图:

sys_user(用户表)

在这里插入图片描述

sys_role(角色表)

在这里插入图片描述

sys_permission(权限表)

在这里插入图片描述

sys_user_role(用户角色表)

在这里插入图片描述

搭建环境

自己搭建哦!!

配置类

import com.qian.chatgpt.entity.info.SecurityInfo;
import com.qian.chatgpt.handler.AuthenticationHandler;
import com.qian.chatgpt.handler.CustomLogoutHandler;
import com.qian.chatgpt.handler.CustomLogoutSuccessHandler;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * @author Qiansheng
 * @date   2023/4/23
 * @description security配置类
 */
@EnableGlobalMethodSecurity(prePostEnabled = true) //开启权限注解全局安全认证
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final UserDetailsService userDetailsService;
    private final AuthenticationHandler authenticationHandler;
    private final com.qian.chatgpt.filter.AuthenticationTokenFilter AuthenticationTokenFilter;
    private final SecurityInfo securityInfo;

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        String[] anonymouPaths = securityInfo.getPermitAllPaths().toArray(new String[0]);
        http
                // CSRF禁用,因为不使用session
                .csrf().disable()
                // 认证失败处理类,指定异常处理实现类
                .exceptionHandling().authenticationEntryPoint(authenticationHandler).and()
                // 基于token,所以不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                // 过滤请求
                .authorizeRequests()
                //设置匿名访问
                .antMatchers(anonymouPaths).permitAll()
                //其他全部拦截认证
                .anyRequest()
                .authenticated()
                .and().logout().addLogoutHandler(new CustomLogoutHandler()).logoutSuccessHandler(new CustomLogoutSuccessHandler());

        //将认证过滤器添加到security中
        http.addFilterBefore(AuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }

    /**
     * 设置加密方式
     *
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

是不是有很多爆红呢?问题比大,我们一个一个来

UserDetailsService:此类是用来从数据库查询用户信息的

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.qian.chatgpt.entity.user.SysPermission;
import com.qian.chatgpt.entity.user.SysUser;
import com.qian.chatgpt.entity.user.SysUserRole;
import com.qian.chatgpt.mapper.SysUserMapper;
import com.qian.chatgpt.service.user.ISysPermissionService;
import com.qian.chatgpt.service.user.ISysUserRoleService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
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;

/**
 * @author : Qiansheng
 * @创建时间 : 2023/4/23
 * @描述信息 : security 对比用户名类
 */
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class UserDetailsServiceImpl implements UserDetailsService {

    private final SysUserMapper sysUserMapper;
    private final ISysPermissionService ISysPermissionService;
    private final ISysUserRoleService sysUserRoleService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUser user = sysUserMapper.selectOne(Wrappers.lambdaQuery(SysUser.class).eq(SysUser::getUsername,username));
        //判断用户名是否存在 如存在就将信息存储
        if (user == null) {
            throw new UsernameNotFoundException("用户不存在: " + username);
        }
        String permissionList="";
        //查询权限
        SysUserRole userRole = sysUserRoleService.getOne(Wrappers.lambdaQuery(SysUserRole.class).eq(SysUserRole::getUserId, user.getId()));
        List<SysPermission> permissions = ISysPermissionService.list(Wrappers.lambdaQuery(SysPermission.class).eq(SysPermission::getRoleId, userRole.getRoleId()));
        for (SysPermission permission : permissions) {
            permissionList=permissionList+permission.getPermissionKey()+",";
        }
        if(permissionList.length()!=0){
           permissionList = permissionList.substring(0,permissionList.length()-1);
        }
        User user1 = new User(user.getUsername(), user.getPassword(), AuthorityUtils.commaSeparatedStringToAuthorityList(permissionList));
        return user1;
    }
}

AuthenticationHandler:此类是用来处理认证失败的异常处理类

import cn.hutool.http.HttpStatus;
import cn.hutool.json.JSONUtil;
import com.qian.chatgpt.util.R;
import com.qian.chatgpt.util.ServletUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.Serializable;
/**
 * @author : Qiansheng
 * @创建时间:2023/4/23
 * @描述信息: security 拦截后返回内容
 */
@Component
@Slf4j
public class AuthenticationHandler implements AuthenticationEntryPoint, Serializable {

    private static final long serialVersionUID = -8970718410437077606L;

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) {
        ServletUtil.renderString(HttpStatus.HTTP_FORBIDDEN, response, JSONUtil.toJsonStr(R.fail(HttpStatus.HTTP_FORBIDDEN, "非法请求:" +e.getMessage())));
    }
}

SecurityInfo: 放行路径信息实体

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.Set;

/**
 * @author   gqs
 * @date     2023/5/16 16:49
 * @description 放行路径信息实体
 */
@Component
@ConfigurationProperties("security")
@Data
public class SecurityInfo {

    /**
     * 放行路径
     */
    private Set<String> permitAllPaths;

}

所有需要放行的路径放在yml配置文件中:
在这里插入图片描述

AuthenticationTokenFilter:用户请求认证拦截器

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.qian.chatgpt.util.JwtUtil;
import com.qian.chatgpt.util.RedisCache;
import io.jsonwebtoken.Claims;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author   gqs
 * @date     2023/5/16 16:49
 * @description 用户请求token认证拦截器
 */
@Component
public class AuthenticationTokenFilter extends OncePerRequestFilter {

    @Resource
    private RedisCache redisCache;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //获取token
        String token = request.getHeader("token");
        if (!StringUtils.hasText(token)) {
            //放行
            filterChain.doFilter(request, response);
            return;
        }

        //使用token从redis获取jwt
        String cacheObject = redisCache.getCacheObject(token);

        //使用jwt获取用户信息并处理
        Claims claims = null;
        try {
            claims = JwtUtil.parseJWT(cacheObject);
        } catch (Exception e) {
            e.printStackTrace();
        }
        if(claims==null){
            filterChain.doFilter(request, response);
            return;
        }
        String usersubject = claims.getSubject();
        JSONObject entries = JSON.parseObject(usersubject);
        JSONArray authorities = entries.getJSONArray("authorities");
        String authlist="";
        for (int i = 0; i < authorities.size(); i++) {
            JSONObject o = (JSONObject) authorities.get(i);
            authlist=authlist+o.getString("authority")+",";
        }
        if(authlist.length()!=0){
            authlist = authlist.substring(0,authlist.length()-1);
        }

        User user = new User(entries.getString("username"), "", AuthorityUtils.commaSeparatedStringToAuthorityList(authlist));
        //存入SecurityContextHolder
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        //放行
        filterChain.doFilter(request, response);
    }
}

好了,配置信息差不多就这些。

登录

登录的service:

public R login(SysUser tUser) {
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(tUser.getUsername(), tUser.getPassword());
        authenticate = authenticationManagerBean.authenticate(authenticationToken);
        if (Objects.isNull(authenticate)) {
            throw new RuntimeException("用户名或密码错误");
        }
        //获取用户信息
        User user = (User) authenticate.getPrincipal();

        //将用户信息存入jwt
        log.info(user.toString());
        // hotool工具解析json少东西 更换为fastjson
        String s1 = JSON.toJSONString(user);
        String jwt = JwtUtil.createJWT(s1,3600000L);

        //将jwt存入redis
        String token = UUID.randomUUID().toString().replaceAll("-","");
        redisCache.setCacheObject(token, jwt,3600000L);

        //把token响应给前端
        HashMap<String, String> map = new HashMap();
        map.put("token", token);
        return R.data(map);
    }

controller

import com.qian.chatgpt.util.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author gqs
 * @作者: Qiansheng
 * @创建时间:2023/4/23
 * @描述信息: TODO
 */
@Slf4j
@RestController
@RequestMapping("/admin")
@CrossOrigin //关闭跨域验证
@Api(tags = "管理员管理")
public class AdminTestController {

    @PreAuthorize("hasAnyAuthority('admin:sel')")
    @ApiOperation(value = "管理员权限测试", notes = "管理员权限测试")
    @GetMapping("/test")
    public R test() {
        return R.success("admin权限测试成功");
    }
}

注意:@PreAuthorize(“hasAnyAuthority(‘admin:sel’)”) 中的admin:sel是权限

OK 结束!有什么问题评论或者留言都可以!

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

面试被虐的小lala

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值