SpringBoot整合SpringSecurity实现进行认证和授权。

  • 阿里云国际版折扣https://www.yundadi.com

  • 阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

    目录

    1. 设置父工程 添加依赖

    2.在子工程通过easyCode创建项目相关包和文件

    3.子项目新建Controllter层并建立BlogLoginController.java

    4.在servic 层定义login 方法并new UsernamePasswordAuthenticationToken对象传入对应用户名密码

    5.自定义实现类实现UserDetailsService接口重写loadUserByUsername方法并查询数据库用户的权限信息封装传入到UserDetails。第4步中UsernamePasswordAuthenticationToken先执行到本步骤。

    6.自定义一个JwtAuthenticationTokenFilter过滤器这个过滤器会去获取每次请求当中的token对token进行解析取出其中的userid。使用userid去redis中获取对应的LoginUser对象。然后封装Authentication对象存入SecurityContextHolder

    7.我们还希望在认证失败或者是授权失败的情况下也能和我们的接口一样返回相同结构的json这样可以让前端能对响应进行统一的处理。要实现这个功能我们需要知道SpringSecurity的异常处理机制。

    8.自定义配置类

    9.授权流程在SpringSecurity中会使用默认的FilterSecurityInterceptor来进行权限校验。

    10.限制访问资源所需权限

    11.其它权限校验方法

    12.自定义权限校验方法

    13.基于配置的权限控制

    14.退出登录

    15.工具类


    1. 设置父工程 添加依赖

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.wang</groupId>
        <artifactId>WangBlog</artifactId>
        <packaging>pom</packaging>
        <version>1.0-SNAPSHOT</version>
        <modules>
            <module>wang-framework</module>
            <module>wang-admin</module>
            <module>wang-blog</module>
        </modules>
    
        <properties>
            <maven.compiler.source>8</maven.compiler.source>
            <maven.compiler.target>8</maven.compiler.target>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        </properties>
    
        <dependencyManagement>
            <dependencies>
                <!-- SpringBoot的依赖配置-->
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-dependencies</artifactId>
                    <version>2.5.0</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
                <!--fastjson依赖-->
                <dependency>
                    <groupId>com.alibaba</groupId>
                    <artifactId>fastjson</artifactId>
                    <version>1.2.33</version>
                </dependency>
                <!--jwt依赖-->
                <dependency>
                    <groupId>io.jsonwebtoken</groupId>
                    <artifactId>jjwt</artifactId>
                    <version>0.9.0</version>
                </dependency>
                <!--mybatisPlus依赖-->
                <dependency>
                    <groupId>com.baomidou</groupId>
                    <artifactId>mybatis-plus-boot-starter</artifactId>
                    <version>3.4.3</version>
                </dependency>
    
                <!--阿里云OSS-->
                <dependency>
                    <groupId>com.aliyun.oss</groupId>
                    <artifactId>aliyun-sdk-oss</artifactId>
                    <version>3.10.2</version>
                </dependency>
    
    
                <dependency>
                    <groupId>com.alibaba</groupId>
                    <artifactId>easyexcel</artifactId>
                    <version>3.0.5</version>
                </dependency>
    
                <dependency>
                    <groupId>io.springfox</groupId>
                    <artifactId>springfox-swagger2</artifactId>
                    <version>2.9.2</version>
                </dependency>
                <dependency>
                    <groupId>io.springfox</groupId>
                    <artifactId>springfox-swagger-ui</artifactId>
                    <version>2.9.2</version>
                </dependency>
            </dependencies>
    
    
        </dependencyManagement>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.1</version>
                    <configuration>
                        <source>${java.version}</source>
                        <target>${java.version}</target>
                        <encoding>${project.build.sourceEncoding}</encoding>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
    
    </project>

    2.在子工程通过easyCode创建项目相关包和文件

    433dbbcc62b44a59997c0e8a6943cc4d.png

    4d5e874ef3f640549c693ef4a3caaaaf.png

    如图然后点击确定即可。 

    子工程pom文件

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>WangBlog</artifactId>
            <groupId>com.wang</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>wang-blog</artifactId>
    
        <properties>
            <maven.compiler.source>8</maven.compiler.source>
            <maven.compiler.target>8</maven.compiler.target>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>com.wang</groupId>
                <artifactId>wang-framework</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-autoconfigure</artifactId>
                <version>2.3.7.RELEASE</version>
                <scope>compile</scope>
            </dependency>
        </dependencies>
    
    </project>

    3.子项目新建Controllter层并建立BlogLoginController.java

    package com.wang.controller;
    
    import com.wang.entity.ResponseResult;
    import com.wang.entity.User;
    import com.wang.enums.AppHttpCodeEnum;
    import com.wang.exception.SystemException;
    import com.wang.service.BlogLoginService;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiOperation;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.util.StringUtils;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * @author 飞
     */
    @RestController
    @Api(tags = "登录",description = "博客登录相关接口")
    public class BlogLoginController {
    
        @Autowired
        private BlogLoginService blogLoginService;
    
        /**
         * @param user
         * @return ResponseResult
         * 登陆
         */
        @PostMapping("/login")
        @ApiOperation(value = "登陆",notes = "登陆")
        public ResponseResult login(@RequestBody User user){
            if (!StringUtils.hasText(user.getUserName())||!StringUtils.hasText(user.getPassword())){
                throw new SystemException(AppHttpCodeEnum.LOGIN_ERROR);
            }
            return blogLoginService.login(user);
        }
    
        @PostMapping("/logout")
        @ApiOperation(value = "退出登陆",notes = "退出登陆")
        public ResponseResult logout(){
            return blogLoginService.logout();
        }
    }
    

    4.在servic 层定义login 方法并new UsernamePasswordAuthenticationToken对象传入对应用户名密码

        @Override
        public ResponseResult login(User user) {
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword());
            Authentication authenticate = authenticationManager.authenticate(authenticationToken);
            //判断是否认证通过
            if(Objects.isNull(authenticate)){
                throw new RuntimeException("用户名或密码错误");
            }
            //获取userid 生成token
            LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
            String userId = loginUser.getUser().getId().toString();
            String jwt = JwtUtil.createJWT(userId);
            //把用户信息存入redis
            redisCache.setCacheObject("bloglogin:"+userId,loginUser);
    
            //把token和userinfo封装 返回
            //把User转换成UserInfoVo
            UserInfoVo userInfoVo = BeanCopyUtils.copyBean(loginUser.getUser(), UserInfoVo.class);
            BlogUserLoginVo vo = new BlogUserLoginVo(jwt,userInfoVo);
            return ResponseResult.okResult(vo);
        }

    5.自定义实现类实现UserDetailsService接口重写loadUserByUsername方法并查询数据库用户的权限信息封装传入到UserDetails。第4步中UsernamePasswordAuthenticationToken先执行到本步骤。

    @Service
    public class UserDetailServiceImpl implements UserDetailsService {
    
        @Autowired
        private UserMapper userMapper;
    
        @Autowired
        private MenuMapper menuMapper;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            //根据用户名查询信息
            LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
            lambdaQueryWrapper.eq(User::getUserName, username);
            User user = userMapper.selectOne(lambdaQueryWrapper);
            if (user == null) {
                throw new RuntimeException("用户不存在");
            }
            //返回用户信息
            //TODO 如果是后天需要权限封装
            if ("1".equals(user.getType())){
                List<String> list = menuMapper.selectPermsByUserId(user.getId());
                return new LoginUser(user, list);
            }
            return new LoginUser(user,null);
        }
    
        }

    loadUserByUsername返回类型为UserDetails我们通过定义一个类实现UserDetails接口重新其中的方法。

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class LoginUser implements UserDetails {
    
        private User user;
    
        private List<String> permissions;
    
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            return null;
        }
    
        @Override
        public String getPassword() {
            return user.getPassword();
        }
    
        @Override
        public String getUsername() {
            return user.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;
        }
    }

    6.自定义一个JwtAuthenticationTokenFilter过滤器这个过滤器会去获取每次请求当中的token对token进行解析取出其中的userid。使用userid去redis中获取对应的LoginUser对象。然后封装Authentication对象存入SecurityContextHolder

    package com.wang.filter;
    
    import com.alibaba.fastjson.JSON;
    import com.wang.entity.LoginUser;
    import com.wang.entity.ResponseResult;
    import com.wang.enums.AppHttpCodeEnum;
    import com.wang.utils.JwtUtil;
    import com.wang.utils.RedisCache;
    import com.wang.utils.WebUtils;
    import io.jsonwebtoken.Claims;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.stereotype.Component;
    import org.springframework.util.StringUtils;
    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;
    import java.util.Objects;
    
    /**
     * @description: 自定义一个JwtAuthenticationTokenFilter过滤器这个过滤器会去获取每次请求当中的token对token进行解析取出其中的userid。
     * 使用userid去redis中获取对应的LoginUser对象。
     * 然后封装Authentication对象存入SecurityContextHolder
     * @author: wang fei
     * @date: 2023/1/13 19:52:24
    **/
    @Component
    public class JwtAuthenticationTokenFilter  extends OncePerRequestFilter {
        @Autowired
        private RedisCache redisCache;
        @Override
        protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
            //获取请求头中的token
            String token = httpServletRequest.getHeader("token");
            if (!StringUtils.hasText(token)) {
                //说明该接口不需要登录  直接放行
                filterChain.doFilter(httpServletRequest, httpServletResponse);
                return;
            }
            //解析userId
            Claims claims = null;
            try {
                claims = JwtUtil.parseJWT(token);
            } catch (Exception e) {
                e.printStackTrace();
                //token超时或token非法
                //给前端提示需要登陆
                ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
                WebUtils.renderString(httpServletResponse, JSON.toJSONString(result));
                return;
            }
    
            String userId = claims.getSubject();
            //从redis中获取用户信息
            LoginUser longinUser = redisCache.getCacheObject("bloglogin:" + userId);
            //如果获取不到
            if (Objects.isNull(longinUser)) {
                //说明登陆过期 提示重新登陆
                ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
                WebUtils.renderString(httpServletResponse, JSON.toJSONString(result));
                return;
            }
            //存入SecurityContextHolder
            UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(longinUser, null, null);
            SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
    
            //放行
            filterChain.doFilter(httpServletRequest, httpServletResponse);
        }
    
        }
    

    7.我们还希望在认证失败或者是授权失败的情况下也能和我们的接口一样返回相同结构的json这样可以让前端能对响应进行统一的处理。要实现这个功能我们需要知道SpringSecurity的异常处理机制。

    在SpringSecurity中如果我们在认证或者授权的过程中出现了异常会被ExceptionTranslationFilter捕获到。在ExceptionTranslationFilter中会去判断是认证失败还是授权失败出现的异常。

    如果是认证过程中出现的异常会被封装成AuthenticationException然后调用AuthenticationEntryPoint对象的方法去进行异常处理。

    ​ 如果是授权过程中出现的异常会被封装成AccessDeniedException然后调用AccessDeniedHandler对象的方法去进行异常处理。

    ​ 所以如果我们需要自定义异常处理我们只需要自定义AuthenticationEntryPointAccessDeniedHandler然后配置给SpringSecurity即可。

    自定义实现类:

    AccessDeniedHandlerImpl.java

    package com.wang.handler.security;
    
    import com.alibaba.fastjson.JSON;
    import com.wang.entity.ResponseResult;
    import com.wang.enums.AppHttpCodeEnum;
    import com.wang.utils.WebUtils;
    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;
    
    /**
     * @author 飞
     *  如果是授权过程中出现的异常会被封装成AccessDeniedException然后调用AccessDeniedHandler对象的方法去进行异常处理。
     */
    @Component
    public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
        @Override
        public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
            e.printStackTrace();
            ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NO_OPERATOR_AUTH);
            //响应给前端
            WebUtils.renderString(httpServletResponse, JSON.toJSONString(result));
        }
    }
    
    AuthenticationEntryPointImpl.java
    package com.wang.handler.security;
    
    import com.alibaba.fastjson.JSON;
    import com.wang.entity.ResponseResult;
    import com.wang.enums.AppHttpCodeEnum;
    import com.wang.utils.WebUtils;
    import org.springframework.security.authentication.BadCredentialsException;
    import org.springframework.security.authentication.InsufficientAuthenticationException;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.web.AuthenticationEntryPoint;
    import org.springframework.stereotype.Component;
    
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    /**
     * @author 飞
     * 如果是认证过程中出现的异常会被封装成AuthenticationException然后调用AuthenticationEntryPoint对象的方法去进行异常处理。
     */
    @Component
    public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
        @Override
        public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
            e.printStackTrace();
            ResponseResult result=null;
            if (e instanceof BadCredentialsException){
                result=ResponseResult.errorResult(AppHttpCodeEnum.LOGIN_ERROR.getCode(), e.getMessage());
            } else if (e instanceof InsufficientAuthenticationException) {
                result=ResponseResult.errorResult(AppHttpCodeEnum.LOGIN_ERROR);
            }else {
                result = ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR.getCode(),"认证或授权失败");
            }
            //响应给前端
            WebUtils.renderString(httpServletResponse, JSON.toJSONString(result));
        }
    }
    

    8.自定义配置类

    package com.wang.config;
    
    import com.wang.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.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 飞
     */
    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
        @Autowired
        private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
        @Autowired
        AuthenticationEntryPoint authenticationEntryPoint;
        @Autowired
        AccessDeniedHandler accessDeniedHandler;
    
        @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("/login").anonymous()
                    //注销接口需要认证才能访问
                    .antMatchers("/logout").authenticated()
    //                .antMatchers("/upload").authenticated()
                    //个人信息接口必须登录后才能访问
                    .antMatchers("/user/userInfo").authenticated()
                    //jwt过滤器测试用如果测试没有问题吧这里删除了
    //                .antMatchers("/link/getAllLink").authenticated()
                    // 除上面外的所有请求全部不需要认证即可访问
                    .anyRequest().permitAll();
    
            //配置异常处理器
            http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)
                    .accessDeniedHandler(accessDeniedHandler);
    
            http.logout().disable();
            //关闭默认的注销功能
            http.logout().disable();
            //把jwtAuthenticationTokenFilter添加到SpringSecurity的过滤器链中
            http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
            //允许跨域
            http.cors();
        }
        @Override
        @Bean
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    }

    9.授权流程在SpringSecurity中会使用默认的FilterSecurityInterceptor来进行权限校验。

    FilterSecurityInterceptor中会从SecurityContextHolder获取其中的Authentication然后获取其中的权限信息。当前用户是否拥有访问当前资源所需的权限。

    所以我们在项目中只需要把当前登录用户的权限信息也存入Authentication。 

    2ca06127c68f4cb8a1b7906fb7374bf1.png

    ​然后设置我们的资源所需要的权限即可。

    10.限制访问资源所需权限

    ​SpringSecurity为我们提供了基于注解的权限控制方案这也是我们项目中主要采用的方式。我们可以使用注解去指定访问对应的资源所需的权限。

    ​ 但是要使用它我们需要先开启相关配置springSecurity里面加。

    @EnableGlobalMethodSecurity(prePostEnabled = true)

    这里使用定义自己的权限校验方法在@PreAuthorize注解中使用我们的方法。

    例如一个接口需要对应的权限才可以访问我们这样写

        /**
         * @description: 分类导出excel  @PreAuthorize("@ps.hasPermission('content:category:export')")判断是否具有content:category:export权限
         * @method: exportLink
         * @author: wang fei
         * @date: 2023/1/13 19:10:13
         * @param: [response]
         * @return: void
        **/
        @PreAuthorize("@ps.hasPermission('content:category:export')")
        @GetMapping("export")
        public void exportLink(HttpServletResponse response){
            //设置下载文件请求头
            try {
                WebUtils.setDownLoadHeader("分类.xlsx",response);
                //提取需要导出的数据
                List<Category> list = categoryService.list();
    
                List<CategoryVo> categoryVos = BeanCopyUtils.copyBeanList(list, CategoryVo.class);
                //把数据写入到excel中
                EasyExcel.write(response.getOutputStream(), ExcelCategoryVo.class).autoCloseStream(Boolean.FALSE).sheet("分类导出")
                        .doWrite(categoryVos);
            } catch (Exception e) {
                //如果出现异常也要响应json
                ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR);
                WebUtils.renderString(response, JSON.toJSONString(result));
            }
    
        }

    @PreAuthorize("@ps.hasPermission('content:category:export')")

    ps为我们定义的beanhasPermission为我们定义的权限判断方法返回类型为boolcontent:category:export为权限信息。

    package com.wang.service.impl;
    
    import com.wang.utils.SecurityUtils;
    import org.springframework.stereotype.Service;
    
    import java.util.List;
    
    /**
     * @BelongsProject: WangBlog
     * @BelongsPackage: com.wang.service.impl
     * @Author: wang fei
     * @CreateTime: 2023-01-13  19:04
     * @Description: TODO
     * @Version: 1.0
     */
    @Service("ps")
    public class PermissionService {
    
        /**
         * @description: 判断当前用户是否具有权限
         * @method: hasPermission
         * @author: wang fei
         * @date: 2023/1/13 19:05:47
         * @param: [permission]
         * @return: boolean
        **/
        public  boolean hasPermission(String permission){
                //如果是超级管理员 直返回true
            if (SecurityUtils.isAdmin()) {
                return true;
            }
                //否则 获取当前用户的权限列表 判断是否存在权限
            List<String> permissions = SecurityUtils.getLoginUser().getPermissions();
            return permissions.contains(permission);
        }
    }
    

    11.其它权限校验方法

    ​ 我们前面都是使用@PreAuthorize注解然后在在其中使用的是hasAuthority方法进行校验。SpringSecurity还为我们提供了其它方法例如hasAnyAuthorityhasRolehasAnyRole等。

    ​ 这里我们先不急着去介绍这些方法我们先去理解hasAuthority的原理然后再去学习其他方法你就更容易理解而不是死记硬背区别。并且我们也可以选择定义校验方法实现我们自己的校验逻辑。

    hasAuthority方法实际是执行到了SecurityExpressionRoothasAuthority大家只要断点调试既可知道它内部的校验原理。

    ​ 它内部其实是调用authenticationgetAuthorities方法获取用户的权限列表。然后判断我们存入的方法参数数据在权限列表中。

    hasAnyAuthority方法可以传入多个权限只有用户有其中任意一个权限都可以访问对应资源。

        @PreAuthorize("hasAnyAuthority('admin','test','system:dept:list')")
        public String hello(){
            return "hello";
        }
    

    hasRole要求有对应的角色才可以访问但是它内部会把我们传入的参数拼接上 ROLE_ 后再去比较。所以这种情况下要用用户对应的权限也要有 ROLE_ 这个前缀才可以。

        @PreAuthorize("hasRole('system:dept:list')")
        public String hello(){
            return "hello";
        }
    
    

    hasAnyRole 有任意的角色就可以访问。它内部也会把我们传入的参数拼接上 ROLE_ 后再去比较。所以这种情况下要用用户对应的权限也要有 ROLE_ 这个前缀才可以。

        @PreAuthorize("hasAnyRole('admin','system:dept:list')")
        public String hello(){
            return "hello";
        }
    

    12.自定义权限校验方法

    ​ 我们也可以定义自己的权限校验方法在@PreAuthorize注解中使用我们的方法。

    @Component("ex")
    public class SGExpressionRoot {
    
        public boolean hasAuthority(String authority){
            //获取当前用户的权限
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            LoginUser loginUser = (LoginUser) authentication.getPrincipal();
            List<String> permissions = loginUser.getPermissions();
            //判断用户权限集合中是否存在authority
            return permissions.contains(authority);
        }
    }
    
    

    在SPEL表达式中使用 @ex相当于获取容器中bean的名字未ex的对象。然后再调用这个对象的hasAuthority方法

        @RequestMapping("/hello")
        @PreAuthorize("@ex.hasAuthority('system:dept:list')")
        public String hello(){
            return "hello";
        }
    

    13.基于配置的权限控制

    ​ 我们也可以在配置类中使用使用配置的方式对资源进行权限控制。

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    //关闭csrf
                    .csrf().disable()
                    //不通过Session获取SecurityContext
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and()
                    .authorizeRequests()
                    // 对于登录接口 允许匿名访问
                    .antMatchers("/user/login").anonymous()
                    .antMatchers("/testCors").hasAuthority("system:dept:list222")
                    // 除上面外的所有请求全部需要鉴权认证
                    .anyRequest().authenticated();
    
            //添加过滤器
            http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    
            //配置异常处理器
            http.exceptionHandling()
                    //配置认证失败处理器
                    .authenticationEntryPoint(authenticationEntryPoint)
                    .accessDeniedHandler(accessDeniedHandler);
    
            //允许跨域
            http.cors();
        }

    14.退出登录

    public ResponseResult logout() {
            //获取token 解析获取userid
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            LoginUser logUser = (LoginUser) authentication.getPrincipal();
            //获取userid
            Long userId = logUser.getUser().getId();
            //删除redis中的用户信息
            redisCache.deleteObject("bloglogin:"+userId);
            return ResponseResult.okResult();
        }

    15.工具类

    BeanCopyUtils.java
    package com.wang.utils;
    
    import org.springframework.beans.BeanUtils;
    
    import java.util.List;
    import java.util.stream.Collectors;
    
    /**
     * @author 飞
     */
    public class BeanCopyUtils {
        private BeanCopyUtils() {
    
        }
        public static <V> V copyBean(Object source, Class<V> clazz){
            //创建目标对象
            V result = null;
            try {
                //反射
                result = clazz.newInstance();
                //实现属性copy
                BeanUtils.copyProperties(source, result);
            } catch (InstantiationException | IllegalAccessException e) {
                throw new RuntimeException(e);
            }
            //返回结果
            return result;
        }
    
        public static <O,V> List<V> copyBeanList(List<O> list, Class<V> clazz){
            return list.stream().map(o->copyBean(o, clazz)).collect(Collectors.toList());
        }
    }
    

    JwtUtil .java

    package com.wang.utils;
    
    import io.jsonwebtoken.Claims;
    import io.jsonwebtoken.JwtBuilder;
    import io.jsonwebtoken.Jwts;
    import io.jsonwebtoken.SignatureAlgorithm;
    
    import javax.crypto.SecretKey;
    import javax.crypto.spec.SecretKeySpec;
    import java.util.Base64;
    import java.util.Date;
    import java.util.UUID;
    
    /**
     * JWT工具类
     * @author 飞
     */
    public class JwtUtil {
    
        //有效期为
        public static final Long JWT_TTL = 24*60 * 60 *1000L;// 60 * 60 *1000  一个小时
        //设置秘钥明文
        public static final String JWT_KEY = "wangfei";
    
        public static String getUUID(){
            String token = UUID.randomUUID().toString().replaceAll("-", "");
            return token;
        }
        
        /**
         * 生成jtw
         * @param subject token中要存放的数据json格式
         * @return
         */
        public static String createJWT(String subject) {
            JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间
            return builder.compact();
        }
    
        /**
         * 生成jtw
         * @param subject token中要存放的数据json格式
         * @param ttlMillis token超时时间
         * @return
         */
        public static String createJWT(String subject, Long ttlMillis) {
            JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间
            return builder.compact();
        }
    
        private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
            SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
            SecretKey secretKey = generalKey();
            long nowMillis = System.currentTimeMillis();
            Date now = new Date(nowMillis);
            if(ttlMillis==null){
                ttlMillis=JwtUtil.JWT_TTL;
            }
            long expMillis = nowMillis + ttlMillis;
            Date expDate = new Date(expMillis);
            return Jwts.builder()
                    .setId(uuid)              //唯一的ID
                    .setSubject(subject)   // 主题  可以是JSON数据
                    .setIssuer("sg")     // 签发者
                    .setIssuedAt(now)      // 签发时间
                    .signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
                    .setExpiration(expDate);
        }
    
        /**
         * 创建token
         * @param id
         * @param subject
         * @param ttlMillis
         * @return
         */
        public static String createJWT(String id, String subject, Long ttlMillis) {
            JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间
            return builder.compact();
        }
    
        public static void main(String[] args) throws Exception {
            String token = "";
            Claims claims = parseJWT(token);
            System.out.println(claims);
        }
    
        /**
         * 生成加密后的秘钥 secretKey
         * @return
         */
        public static SecretKey generalKey() {
            byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
            SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
            return key;
        }
        
        /**
         * 解析
         *
         * @param jwt
         * @return
         * @throws Exception
         */
        public static Claims parseJWT(String jwt) throws Exception {
            SecretKey secretKey = generalKey();
            return Jwts.parser()
                    .setSigningKey(secretKey)
                    .parseClaimsJws(jwt)
                    .getBody();
        }
    
    
    }
    PathUtils.java
    package com.wang.utils;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.UUID;
    
    /**
     * @author 飞
     */
    public class PathUtils {
    
        public static String generateFilePath(String fileName){
            //根据日期生成路径   2022/1/15/
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd/");
            String datePath = sdf.format(new Date());
            //uuid作为文件名
            String uuid = UUID.randomUUID().toString().replaceAll("-", "");
            //后缀和文件后缀一致
            int index = fileName.lastIndexOf(".");
            // test.jpg -> .jpg
            String fileType = fileName.substring(index);
            return new StringBuilder().append(datePath).append(uuid).append(fileType).toString();
        }
    }
    RedisCache.java
    package com.wang.utils;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.BoundSetOperations;
    import org.springframework.data.redis.core.HashOperations;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.core.ValueOperations;
    import org.springframework.stereotype.Component;
    
    import java.util.*;
    import java.util.concurrent.TimeUnit;
    
    
    /**
     * @author 飞
     */
    @Component
    public class RedisCache
    {
        @Autowired
        public RedisTemplate redisTemplate;
    
        /**
         * 缓存基本的对象Integer、String、实体类等
         *
         * @param key 缓存的键值
         * @param value 缓存的值
         */
        public <T> void setCacheObject(final String key, final T value)
        {
            redisTemplate.opsForValue().set(key, value);
        }
    
        /**
         * 缓存基本的对象Integer、String、实体类等
         *
         * @param key 缓存的键值
         * @param value 缓存的值
         * @param timeout 时间
         * @param timeUnit 时间颗粒度
         */
        public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
        {
            redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
        }
    
        /**
         * 设置有效时间
         *
         * @param key Redis键
         * @param timeout 超时时间
         * @return true=设置成功false=设置失败
         */
        public boolean expire(final String key, final long timeout)
        {
            return expire(key, timeout, TimeUnit.SECONDS);
        }
    
        /**
         * 设置有效时间
         *
         * @param key Redis键
         * @param timeout 超时时间
         * @param unit 时间单位
         * @return true=设置成功false=设置失败
         */
        public boolean expire(final String key, final long timeout, final TimeUnit unit)
        {
            return redisTemplate.expire(key, timeout, unit);
        }
    
        /**
         * 获得缓存的基本对象。
         *
         * @param key 缓存键值
         * @return 缓存键值对应的数据
         */
        public <T> T getCacheObject(final String key)
        {
            ValueOperations<String, T> operation = redisTemplate.opsForValue();
            return operation.get(key);
        }
    
        /**
         * 删除单个对象
         *
         * @param key
         */
        public boolean deleteObject(final String key)
        {
            return redisTemplate.delete(key);
        }
    
        /**
         * 删除集合对象
         *
         * @param collection 多个对象
         * @return
         */
        public long deleteObject(final Collection collection)
        {
            return redisTemplate.delete(collection);
        }
    
        /**
         * 缓存List数据
         *
         * @param key 缓存的键值
         * @param dataList 待缓存的List数据
         * @return 缓存的对象
         */
        public <T> long setCacheList(final String key, final List<T> dataList)
        {
            Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
            return count == null ? 0 : count;
        }
    
        /**
         * 获得缓存的list对象
         *
         * @param key 缓存的键值
         * @return 缓存键值对应的数据
         */
        public <T> List<T> getCacheList(final String key)
        {
            return redisTemplate.opsForList().range(key, 0, -1);
        }
    
        /**
         * 缓存Set
         *
         * @param key 缓存键值
         * @param dataSet 缓存的数据
         * @return 缓存数据的对象
         */
        public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
        {
            BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
            Iterator<T> it = dataSet.iterator();
            while (it.hasNext())
            {
                setOperation.add(it.next());
            }
            return setOperation;
        }
    
        /**
         * 获得缓存的set
         *
         * @param key
         * @return
         */
        public <T> Set<T> getCacheSet(final String key)
        {
            return redisTemplate.opsForSet().members(key);
        }
    
        /**
         * 缓存Map
         *
         * @param key
         * @param dataMap
         */
        public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
        {
            if (dataMap != null) {
                redisTemplate.opsForHash().putAll(key, dataMap);
            }
        }
    
        /**
         * 获得缓存的Map
         *
         * @param key
         * @return
         */
        public <T> Map<String, T> getCacheMap(final String key)
        {
            return redisTemplate.opsForHash().entries(key);
        }
    
        /**
         * 往Hash中存入数据
         *
         * @param key Redis键
         * @param hKey Hash键
         * @param value 值
         */
        public <T> void setCacheMapValue(final String key, final String hKey, final T value)
        {
            redisTemplate.opsForHash().put(key, hKey, value);
        }
    
        /**
         * 获取Hash中的数据
         *
         * @param key Redis键
         * @param hKey Hash键
         * @return Hash中的对象
         */
        public <T> T getCacheMapValue(final String key, final String hKey)
        {
            HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
            return opsForHash.get(key, hKey);
        }
    
        /**
         * 删除Hash中的数据
         * 
         * @param key
         * @param hkey
         */
        public void delCacheMapValue(final String key, final String hkey)
        {
            HashOperations hashOperations = redisTemplate.opsForHash();
            hashOperations.delete(key, hkey);
        }
    
        /**
         * 获取多个Hash中的数据
         *
         * @param key Redis键
         * @param hKeys Hash键集合
         * @return Hash对象集合
         */
        public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
        {
            return redisTemplate.opsForHash().multiGet(key, hKeys);
        }
    
        /**
         * 获得缓存的基本对象列表
         *
         * @param pattern 字符串前缀
         * @return 对象列表
         */
        public Collection<String> keys(final String pattern)
        {
            return redisTemplate.keys(pattern);
        }
    
        public void incrementCacheMapValue(String key,String hKey,long v){
            redisTemplate.boundHashOps(key).increment(hKey, v);
        }
    }
    SecurityUtils.java
    package com.wang.utils;
    
    import com.wang.entity.LoginUser;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.context.SecurityContextHolder;
    
    /**
     * @author 飞
     */
    public class SecurityUtils
    {
    
        /**
         * 获取用户
         **/
        public static LoginUser getLoginUser()
        {
            return (LoginUser) getAuthentication().getPrincipal();
        }
    
        /**
         * 获取Authentication
         */
        public static Authentication getAuthentication() {
            return SecurityContextHolder.getContext().getAuthentication();
        }
    
        public static Boolean isAdmin(){
            Long id = getLoginUser().getUser().getId();
            return id != null && 1L == id;
        }
    
        public static Long getUserId() {
            return getLoginUser().getUser().getId();
        }
    }

    WebUtils.java

    package com.wang.utils;
    
    import org.springframework.web.context.request.RequestContextHolder;
    
    import javax.servlet.ServletContext;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.UnsupportedEncodingException;
    import java.net.URLEncoder;
    
    /**
     * @author 飞
     */
    public class WebUtils
    {
        /**
         * 将字符串渲染到客户端
         *
         * @param response 渲染对象
         * @param string 待渲染的字符串
         * @return null
         */
        public static void renderString(HttpServletResponse response, String string) {
            try
            {
                response.setStatus(200);
                response.setContentType("application/json");
                response.setCharacterEncoding("utf-8");
                response.getWriter().print(string);
            }
            catch (IOException e)
            {
                e.printStackTrace();
            }
        }
    
        public static void setDownLoadHeader(String filename, HttpServletResponse response) throws UnsupportedEncodingException {
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding("utf-8");
            String fname= URLEncoder.encode(filename,"UTF-8").replaceAll("\\+", "%20");
            response.setHeader("Content-disposition","attachment; filename="+fname);
        }
    }

  • 阿里云国际版折扣https://www.yundadi.com

  • 阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
    标签: Spring