欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

spring boot整合spring security实现基于rabc的权限控制

程序员文章站 2024-03-26 09:36:05
...

1.spring security基于rbac权限控制

1.1 引入依赖

<dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        <dependency>
            <groupId>com.mayday</groupId>
            <artifactId>mayday_common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
            <version>2.1.5</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

1.2配置文件

server:
  port: 9003
spring:
  application:
    name: mayday-auth
  datasource:
    driverClassName: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/blog?useUnicode=true&characterEncoding=utf8&useSSL=false
    username: root
    password: admin
  redis:
    host: 127.0.0.1
eureka:
  client:
    serviceUrl:
      defaultZone: http://127.0.0.1:9001/eureka/
  instance:
    prefer-ip-address: true

mybatis:
  type-aliases-package: com.mayday.auth.pojo
  configuration:
    map-underscore-to-camel-case: true

rsa:
  key:
    pubKeyFile: D:/document/key/key_rsa.pub
    priKeyFile: D:/document/key/key_rsa
  allowPaths:
    - /auth/login/**
    - /auth/code/**

1.3 创建pojo类

@Data
@Entity
@Table(name = "tb_user")
public class User implements UserDetails {
    @Id
    @KeySql(useGeneratedKeys = true)
    private Long id;
    private String username;
    private String password;
    private Boolean status;
    private String phone;
    @Transient
    private String checkCode;
    private List<Role> roles;

    @Override
    @JsonIgnore
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return roles.stream().map(Role::getPermissions).findAny().get();

    }


    @Override
    @JsonIgnore
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    @JsonIgnore
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    @JsonIgnore
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    @JsonIgnore
    public boolean isEnabled() {
        return true;
    }
}
@Data
@Entity
@Table(name = "tb_role")
public class Role {
    @Id
    @KeySql(useGeneratedKeys = true)
    private Long id;
    @Column(name = "role_name")
    private String roleName;
    @Column(name = "role_desc")
    private String roleDesc;
    private List<Permission> permissions;
}
@Data
@Entity
@Table(name = "tb_permission")
public class Permission implements GrantedAuthority {
    @Id
    @KeySql(useGeneratedKeys = true)
    private Long id;
    @Column(name = "permission_name")
    private String permissionName;
    @Column(name = "permission_desc")
    private String permissionDesc;

    @Override
    @JsonIgnore
    public String getAuthority() {
        return permissionName;
    }
}

1.4 编写dao和service层

DAO层

user接口
public interface UserMapper extends Mapper<User> {
}

role接口
public interface RoleMapper extends Mapper<Role> {
    @Select("select r.id,r.role_name,r.role_desc from tb_role r left join tb_user_role ur on  r.id = ur.role_id where ur.user_id = #{userId}")
    List<Role> selectAllByUserId(Long userId);
}

permission接口
public interface PermissionMapper extends Mapper<Permission> {
    @Select("select p.id,p.permission_name,p.permission_desc from tb_permission p left join tb_role_permission rp on  p.id = rp.permission_id where rp.role_id = #{roleId}")
    List<Permission> selectAllByRoleId(Long roleId);
}

service 层

@Service
public class UserService implements UserDetailsService {
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private PermissionMapper permissionMapper;
    @Autowired
    private RoleMapper roleMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = new User();
        user.setUsername(username);
        User currentUser = userMapper.selectOne(user);
        if (currentUser == null) {
            return null;
        }
        //设置用户的角色
        List<Role> roles = roleMapper.selectAllByUserId(currentUser.getId());
        if (!CollectionUtils.isEmpty(roles)) {
            for (Role role : roles) {
                //查询每个角色对应的权限
                List<Permission> permissions = permissionMapper.selectAllByRoleId(role.getId());
                if (!CollectionUtils.isEmpty(permissions)) {
                    role.setPermissions(permissions);
                }
            }
            currentUser.setRoles(roles);
        }
        return currentUser;
    }
}

1.5 controller层

@RestController
@RequestMapping("/auth")
public class UserController {
    @Autowired
    private UserService userService;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private RsaKeyProperties rsaKeyProperties;

    private static final String REDIS_PREFIX = "mayday:login:code";

    private static final int OUT_TIME = 30;
    private static final String TOKEN_PREFIX = "Bearer ";

    /**
     * 生成验证码
     *
     * @param response
     * @param request
     * @throws IOException
     */
    @GetMapping("/code")
    public void captcha(HttpServletResponse response, HttpServletRequest request) throws IOException {
        response.setHeader("Cache-Control", "no-store, no-cache");
        response.setContentType("image/jpeg");
        // 生成图片验证码
        BufferedImage image = CaptchaUtil.createImage();
        // 生成文字验证码
        String randomText = CaptchaUtil.drawRandomText(image);
        // 保存到验证码到 redis 有效期两分钟
        redisTemplate.opsForValue().set(REDIS_PREFIX, randomText.toLowerCase(), 1, TimeUnit.MINUTES);
        ServletOutputStream out = response.getOutputStream();
        ImageIO.write(image, "jpeg", out);
    }

    /**
     * 登录
     *
     * @param user
     * @return
     */
    @PostMapping("/login")
    public Result login(@RequestBody User user) {
        //检查验证码是否正确
        String checkCode = user.getCheckCode();
        String redisCode = redisTemplate.opsForValue().get(REDIS_PREFIX);
        if (StringUtils.isEmpty(checkCode) || !redisCode.equals(checkCode)) {
            return new Result(false, StatusCode.CHECK_CODE_NOT_EXIT.getCode(), StatusCode.CHECK_CODE_NOT_EXIT.getMsg());
        }
        //验证登录
        User currentUser = (User) userService.loadUserByUsername(user.getUsername());
        if (currentUser == null) {
            return new Result(false, StatusCode.USERNAME_NOT_FOUND.getCode(), StatusCode.USERNAME_NOT_FOUND.getMsg());
        }
        //检验密码是否输入正确
        if (new BCryptPasswordEncoder().matches(user.getPassword(), currentUser.getPassword())) {
            return new Result(false, StatusCode.LOGIN_ERROR.getCode(), StatusCode.LOGIN_ERROR.getMsg());
        }
        String token = TOKEN_PREFIX + JwtUtils.generateTokenExploreInMinutes(currentUser, rsaKeyProperties.getPrivateKey(), OUT_TIME);
        return new Result(true, StatusCode.OK.getCode(), StatusCode.OK.getMsg(), token);
    }

    /**
     * 权限测试接口
     * @return
     */
    @GetMapping("test")
    @PreAuthorize("hasAnyAuthority('product_list')")
    public String test() {
        return "test success";
    }
}

2 配置

2.1 创建配置类

@Data
@ConfigurationProperties("rsa")
public class AllowPathsProperties {
    private List<String> allowPaths;
}
@Data
@ConfigurationProperties("rsa.key")
public class RsaKeyProperties {

    private String pubKeyFile;
    private String priKeyFile;


    public PublicKey publicKey;
    private PrivateKey privateKey;

    /**
     * 在创建对象之后执行的方法
     *
     * @throws Exception
     */
    @PostConstruct
    public void createRsaKey() throws Exception {
        publicKey = RsaUtils.getPublicKey(pubKeyFile);
        privateKey = RsaUtils.getPrivateKey(priKeyFile);
    }
}

创建Security配置类

@Slf4j
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserService userService;
    @Autowired
    private RsaKeyProperties rsaKeyProperties;
    @Autowired
    private AllowPathsProperties allowPathsProperties;


    /**
     * 解决无法直接注入AuthenticationManager
     *
     * @return
     * @throws Exception
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * 装载BCrypt密码编码器 密码加密
     *
     * @return
     */
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 设置UserDetailsService   使用BCrypt进行密码的hash
        auth.userDetailsService(userService).passwordEncoder(this.passwordEncoder());
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                // 由于使用的是JWT,我们这里不需要csrf
                .csrf().disable()
                // 过滤请求
                .authorizeRequests()
                // 对于登录login 图标 要允许匿名访问
                .antMatchers(allowPathsProperties.getAllowPaths().stream().toArray(String[]::new)).anonymous()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated()
                .and()
                .headers().frameOptions().disable()
                .and().addFilter(new LoginFilter(super.authenticationManager(), rsaKeyProperties))
                //基于token,不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        ;
    }
}

2.2创建过滤器

public class LoginFilter extends BasicAuthenticationFilter {

    private RsaKeyProperties rsaKeyProperties;

    public LoginFilter(AuthenticationManager authenticationManager, RsaKeyProperties rsaKeyProperties) {
        super(authenticationManager);
        this.rsaKeyProperties = rsaKeyProperties;
    }

    @Override
    public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        String header = request.getHeader("Authorization");
        if (header == null || !header.startsWith("Bearer ")) {
            //如果携带错误的token,则给用户提示请登录!
            chain.doFilter(request, response);
            response.setContentType("application/json;charset=utf-8");
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            PrintWriter out = response.getWriter();
            Map resultMap = new HashMap();
            resultMap.put("code", HttpServletResponse.SC_FORBIDDEN);
            resultMap.put("msg", "请登录!");
            out.write(new ObjectMapper().writeValueAsString(resultMap));
            out.flush();
            out.close();
        } else {
            //如果携带了正确格式的token要先得到token
            String token = header.replace("Bearer ", "");
            //验证token是否正确
            Payload<User> payload = JwtUtils.genInfoFromToken(token, rsaKeyProperties.getPublicKey(), User.class);
            User user = payload.getUserInfo();
            if (user != null) {
                UsernamePasswordAuthenticationToken authResult = new UsernamePasswordAuthenticationToken(user.getUsername(), null, user.getAuthorities());
                SecurityContextHolder.getContext().setAuthentication(authResult);
                chain.doFilter(request, response);
            }
        }
    }
}

3启动类

@SpringBootApplication
@MapperScan("com.mayday.auth.dao")
@EnableDiscoveryClient
@EnableConfigurationProperties({RsaKeyProperties.class, AllowPathsProperties.class})
public class AuthApplication {
    public static void main(String[] args) {
        SpringApplication.run(AuthApplication.class);
    }
}

4自定义异常处理

/**
 * 认证失败处理类 返回401
 *
 * @author lihaodong
 */
@Slf4j
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException {
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        //如果携带错误的token,则给用户提示请登录!
        PrintWriter out = response.getWriter();
        Result result = new Result(false, StatusCode.UN_AUTHORIZATION);
        out.write(new ObjectMapper().writeValueAsString(result));
        out.flush();
        out.close();
    }
}
/**
 * 权限不足认证
 */
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
        //如果携带错误的token,则给用户提示请登录!
        PrintWriter out = httpServletResponse.getWriter();
        Result result = new Result(false, StatusCode.ACCESS_ERROR);
        out.write(new ObjectMapper().writeValueAsString(result));
        out.flush();
        out.close();
    }
}

在webSecurityConfig配置类上加上

 @Autowired
    private AuthenticationEntryPointImpl authenticationEntryPoint;
    @Autowired
    private AccessDeniedHandlerImpl accessDeniedHandler;
     @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                // 由于使用的是JWT,我们这里不需要csrf
                .csrf().disable()
                //认证失败处理类
                .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).accessDeniedHandler(accessDeniedHandler)
                }
}

5 测试

spring boot整合spring security实现基于rabc的权限控制
将生成的token填入测试接口中
spring boot整合spring security实现基于rabc的权限控制
spring boot整合spring security实现基于rabc的权限控制
spring boot整合spring security实现基于rabc的权限控制