spring security实战之路

简述

目前主流的权限校验框架很多,其实每一种框架校验都有优缺点,我们只有不停的学习才能不停的成长,同时提高自己的编码能力和逻辑思维。接下来我们具有spring security实现登录状态校验。

初始化工程

springBoot初始化工程,初始化时,我们添加一下框架配置:

maven配置

<?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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.lgh</groupId>
    <artifactId>project-study</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>project-study</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.2.12</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>${
    
    swagger.ui}</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>${
    
    swagger.ui}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

application配置

server:
  port: 8080
spring:
  security:
    user:
      name: root
      password: root
  mvc:
    throw-exception-if-no-handler-found: true
  resources:
    add-mappings: false
  datasource:
    username: root
    password: root
    url: jdbc:mysql://127.0.0.1:3306/lgh?serverTimezone=UTC&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver
logging:
  level:
    com.mybatis.mapper: debug
mybatis:
  mapper-locations: classpath:mapper/*.xml


main函数配置

配置spring securit启动项 @EnableWebSecurity

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@SpringBootApplication
@EnableWebSecurity
@MapperScan("com.lgh.mapper")
@EnableSwagger2
public class RunApplication {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(RunApplication.class, args);
    }

}

代码实战

在看这边之前,大家如果不懂spring security的简单原理,建议还是看一下官网地址,大体了解一下spring官方文档,若你想更简洁一点了解,可以查看howtodoinjava,这里不多啰嗦。我简介一下。spring security主要两个功能点:认证和授权。

SecurityContext讲解

其中最关键的对SecurityContext存储上下文的基本数据,是当前线程共有,即ThreadLocal,spring security是针对配置资源和访问角色进行认证和鉴权。

Authentication授权讲解

Authentication对象org.springframework.security.core.Authentication接口已经基本spring security生命provider所有对象,几个对象非常关注。

  1. getAuthorities获取角色权限列表,继承GrantedAuthority,基本spring类型数据。@RolesAllowed 对应
  2. getCredentials获取加密数据,也就是密码,我们这里是token校验,所以不用。
  3. getDetails获取详情数据,这里可以保存用户详细信息 ,可以保存用户基本信息
  4. getPrincipal获取重要角色,保存重要信息即可 @AuthenticationPrincipal可入参获取信息

接下来我们实现Authentication接口,实现鉴权功能

package com.lgh.common.authority.authentication;

import com.lgh.common.authority.entity.UserDetail;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;

import java.util.Collection;
import java.util.List;

public class MyAuthentication implements Authentication {
    
    
	// 存储用户信息
    private UserDetail userDetail;
    // 存储角色权限
    private List<SimpleGrantedAuthority> authorities;

    public MyAuthentication(UserDetail userDetail, List<SimpleGrantedAuthority> authorities) {
    
    
        this.userDetail = userDetail;
        this.authorities = authorities;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
    
    
        return this.authorities;
    }

    @Override
    public Object getCredentials() {
    
    
        return null;
    }

    @Override
    public Object getDetails() {
    
    
        return this.userDetail;
    }

    @Override
    public Object getPrincipal() {
    
    
        return this.userDetail;
    }

    @Override
    public boolean isAuthenticated() {
    
    
        return false;
    }

    @Override
    public void setAuthenticated(boolean b) throws IllegalArgumentException {
    
    

    }

    @Override
    public String getName() {
    
    
        return null;
    }
}

用户实体类

package com.lgh.common.authority.entity;

public class UserDetail {
    
    
    private long id;
    private String name;

    public long getId() {
    
    
        return id;
    }

    public void setId(long id) {
    
    
        this.id = id;
    }

    public String getName() {
    
    
        return name;
    }

    public void setName(String name) {
    
    
        this.name = name;
    }
}

过滤认证功能实现

继承OncePerRequestFilter一次请求过滤器,实现简单过滤,若校验用户失败,直接返回异常。

扫描二维码关注公众号,回复: 12274276 查看本文章
package com.lgh.common.authority.filter;

import com.lgh.common.authority.authentication.MyAuthentication;
import com.lgh.common.authority.entity.UserDetail;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
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.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@Component
public class MyAuthenticationFilter extends OncePerRequestFilter {
    
    

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    
    
        // 获取header头信息,发起用户验证
        UserDetail userDetail = new UserDetail();
        userDetail.setId(1);
        userDetail.setName("test");
        List<SimpleGrantedAuthority> roles = Arrays.asList(new String[]{
    
    "ADMIN", "USER", "GUEST"})
                .stream()
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
        MyAuthentication myAuthentication = new MyAuthentication(userDetail, roles);
        SecurityContextHolder.getContext().setAuthentication(myAuthentication);
        // 验证异常则抛出异常
      /*  boolean valid = false;
        if (valid) {
            response.getWriter().print("deny");
            return;
        }*/
        filterChain.doFilter(request, response);
    }
}

配置WebSecurityConfigurerAdapter

package com.lgh.common.authority.config;

import com.lgh.common.authority.filter.MyAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableGlobalMethodSecurity(jsr250Enabled = true, securedEnabled = true)
public class WebMvcConfigure extends WebSecurityConfigurerAdapter {
    
    

    @Autowired
    private MyAuthenticationFilter myAuthenticationFilter;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        http.csrf().disable()
                .addFilterBefore(myAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
                .authorizeRequests()
                .antMatchers("/")
                .permitAll()
                .and()
                .formLogin().disable();
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
    
    
        web.ignoring()
                .antMatchers("/v2/api-docs",
                        "/swagger-resources/configuration/ui",
                        "/swagger-resources",
                        "/swagger-resources/configuration/security",
                        "/swagger-ui.html",
                        "/webjars/**",
                        "/actuator");
    }
}

测试

经过上面的配置,已经配置好了spring security基本校验token来验证用户登录,接下来来验证权限校验。

编写controller控制类

package com.lgh.controller;

import com.lgh.common.authority.entity.UserDetail;
import com.lgh.common.result.CommonResult;
import com.lgh.common.result.inter.IResult;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.security.RolesAllowed;

@RestController
@RequestMapping("/user")
public class UserController {
    
    


    @GetMapping("/detail")
    public IResult<UserDetail> getUser(@AuthenticationPrincipal UserDetail userDetail) {
    
    
        return CommonResult.successData(userDetail);
    }

    @PatchMapping("/update")
    @RolesAllowed("user:update")
    public void updateUser() {
    
    
        // do something
    }
}

统一报文返回设置

返回接口规范

package com.lgh.common.result.inter;
public interface IResult<T> {
    
    
    int getCode();
    String getMessage();
    T getData();
}

统一返回对象

package com.lgh.common.result;

import com.lgh.common.result.inter.IResult;

public class CommonResult<T> implements IResult<T> {
    
    
    private int code;
    private String message;
    private T data;

    public CommonResult(int code, String message) {
    
    
        this.code = code;
        this.message = message;
    }

    public CommonResult(int code, String message, T data) {
    
    
        this.code = code;
        this.message = message;
        this.data = data;
    }

    public static IResult success() {
    
    
        return new CommonResult(ResultEnum.SUCCESS.code, ResultEnum.SUCCESS.message);
    }

    public static <T> IResult successData(T data) {
    
    
        return new CommonResult(ResultEnum.SUCCESS.code, ResultEnum.SUCCESS.message, data);
    }

    public static IResult deny() {
    
    
        return new CommonResult(ResultEnum.DENY.code, ResultEnum.DENY.message);
    }

    @Override
    public int getCode() {
    
    
        return code;
    }

    @Override
    public String getMessage() {
    
    
        return message;
    }

    @Override
    public T getData() {
    
    
        return this.data;
    }

    public void setData(T data) {
    
    
        this.data = data;
    }
}

几种常见枚举型设置

package com.lgh.common.result;

public enum ResultEnum {
    
    
    SUCCESS(200, "成功"),
    DENY(403, "无权访问"),
    UNKNOW(401, "无权访问");

    public int code;
    public String message;

    ResultEnum(int code, String message) {
    
    
        this.code = code;
        this.message = message;
    }
}

统一异常处理编写

package com.lgh.common.result;

import com.lgh.common.result.inter.IResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;

import java.nio.file.AccessDeniedException;

@RestControllerAdvice
public class ExceptionHandlerAspect {
    
    

    @ExceptionHandler(Exception.class)
    public <T> IResult<T> exe(Exception ex) {
    
    
        if (AccessDeniedException.class.isAssignableFrom(ex.getClass())) {
    
    
            return CommonResult.deny();
        }
        if (NoHandlerFoundException.class.isAssignableFrom(ex.getClass())) {
    
    
            new CommonResult(404, ex.getMessage());
        }
        return new CommonResult(ResultEnum.UNKNOW.code, ResultEnum.UNKNOW.message);
    }
}

工程源码

github

猜你喜欢

转载自blog.csdn.net/soft_z1302/article/details/112722075