SpringSecurity授权

认证知识请看:SpringSecurity + JWT实现登录认证-CSDN博客

一、RBAC权限模型

在RBAC权限模型下存在用户、角色、权限三个关系,一个角色可以拥有多个权限,一个用户可以是多个角色,所以用户可以同时拥有多个角色的权限。在数据库中表示为用户表、角色表、权限表,同时还应该有两张关联表分别与用户和角色、角色和权限进行关联。

1、用户表

2、角色表

3、权限表

4、用户角色关联表

5、角色权限关联表

6、查询用户拥有权限的SQL

select distinct menuName FROM role r
left join user_role ur on r.roleId = ur.roleId
left join role_menu rm on r.roleId = rm.roleId 
left join menu m on rm.menuId = m.menuId
where ur.userId = 1

二、实现授权

1、创建查询权限方法(mapper)

public interface UserMapper extends BaseMapper<User> {
    List<String> getAuthentications(int userId);
}


//xml
<mapper namespace="com.zxc.mapper.UserMapper">
    <select id="getAuthentications" parameterType="int" resultType="string">
        select distinct menuName FROM role r
                                          left join user_role ur on r.roleId = ur.roleId
                                          left join role_menu rm on r.roleId = rm.roleId
                                          left join menu m on rm.menuId = m.menuId
        where ur.userId = #{userId}
    </select>
</mapper>

2、登录校验时添加权限


@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<User>().eq(User::getName, username);
        User user = userMapper.selectOne(queryWrapper);
        if(null == user){
            throw new RuntimeException("用户名或密码错误");
        }
        //用户权限
//        ArrayList<String> list = new ArrayList<>();
//        list.add("test");
//        list.add("admin");
        Integer id = user.getId();
        List<String> list = userMapper.getAuthentications(id);
        LoginUser loginUser = new LoginUser(user,list);

        return loginUser;
    }
}

3、在SecurityContextHolder存入权限

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    @Autowired
    private RedisTemplate<String,String> redisTemplate;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //从请求头中提取出用户id
        String jwt = request.getHeader("token");
        //id不存在
        if(!StringUtils.hasText(jwt)){
            filterChain.doFilter(request,response);
            return;
        }
        //解析jwt
        String id = null;
        try {
            Claims claims = JwtUtils.parseJWT(jwt);
            id = claims.getSubject();
        } catch (Exception e) {
            e.printStackTrace();
            filterChain.doFilter(request,response);
        }
        //使用用户id从redis里面提取出用户信息
        String jsonstring = redisTemplate.opsForValue().get("userId:" + id);
        LoginUser loginUser = JSON.parseObject(jsonstring, LoginUser.class);
        //TODO:json转对象后数丢失,重新赋权(暂时未解决)
        loginUser.getAuthorityList().clear();
        loginUser.getPermission().forEach(p -> loginUser.getAuthorityList().add(new SimpleGrantedAuthority(p)));
        //将用户信息存入securityContextHolder
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        filterChain.doFilter(request,response);
    }
}

4、开启权限认证

开启权限认证:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) //开启授权验证
public class SecurityConfig {
    @Bean
    public PasswordEncoder PasswordEncoder(){
        return new BCryptPasswordEncoder();
    }


    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
        return config.getAuthenticationManager();
    }

    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    /**
     * 配置认证规则
     * @param http
     * @return
     * @throws Exception
     */
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                // 禁用basic明文验证
                //.httpBasic().disable()
                // 前后端分离架构不需要csrf保护
                .csrf().disable()
                // 禁用默认登录页
                //.formLogin().disable()
                // 禁用默认登出页
                //.logout().disable()
                // 设置异常的EntryPoint,如果不设置,默认使用Http403ForbiddenEntryPoint
                //.exceptionHandling(exceptions -> exceptions.authenticationEntryPoint(invalidAuthenticationEntryPoint))
                // 前后端分离是无状态的,不需要session了,直接禁用。
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests
                        // 允许所有OPTIONS请求
                        //.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                        // 允许直接访问授权登录接口
                        .requestMatchers(HttpMethod.POST, "/user/login").anonymous() //匿名
                        .requestMatchers(HttpMethod.GET,"/*/hello").permitAll() //允许所有
                        // 允许 SpringMVC 的默认错误地址匿名访问
                        //.requestMatchers("/error").permitAll()
                        // 其他所有接口必须有Authority信息,Authority在登录成功后的UserDetailsImpl对象中默认设置“ROLE_USER”
                        //.requestMatchers("/**").hasAnyAuthority("ROLE_USER")
                        // 允许任意请求被已登录用户访问,不检查Authority
                        .anyRequest().authenticated()) //任意用户认证之后都可以访问
                //.authenticationProvider(authenticationProvider())
                // 加我们自定义的过滤器,替代UsernamePasswordAuthenticationFilter
                .addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
              

        return http.build();
    }
}

在controller配置权限:

@PreAuthorize("hasAnyAuthority('test')")
    @GetMapping("/hello")
    public String hello(){
        return "hello";
    }

三、自定义异常处理

1、认证异常处理

@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
    //认证异常处理
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        //处理异常
        ResponseResult responseResult = new ResponseResult(HttpStatus.UNAUTHORIZED.value(), "认证失败", authException.getMessage());
        String jsonString = JSON.toJSONString(responseResult);
        WebUtils.renderString(response,jsonString);
    }
}

2、授权异常处理

@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
    //授权异常处理
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        //处理异常
        ResponseResult responseResult = new ResponseResult(HttpStatus.FORBIDDEN.value(), "没有访问权限", accessDeniedException.getMessage());
        String jsonString = JSON.toJSONString(responseResult);
        WebUtils.renderString(response,jsonString);
    }
}

3、配置自定义异常处理

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) //开启授权验证
public class SecurityConfig {
    @Bean
    public PasswordEncoder PasswordEncoder(){
        return new BCryptPasswordEncoder();
    }


    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
        return config.getAuthenticationManager();
    }

    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    //认证失败
    @Autowired
    private AuthenticationEntryPoint authenticationEntryPoint;

    //授权失败
    @Autowired
    private AccessDeniedHandler accessDeniedHandler;

    /**
     * 配置认证规则
     * @param http
     * @return
     * @throws Exception
     */
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                // 禁用basic明文验证
                //.httpBasic().disable()
                // 前后端分离架构不需要csrf保护
                .csrf().disable()
                // 禁用默认登录页
                //.formLogin().disable()
                // 禁用默认登出页
                //.logout().disable()
                // 设置异常的EntryPoint,如果不设置,默认使用Http403ForbiddenEntryPoint
                //.exceptionHandling(exceptions -> exceptions.authenticationEntryPoint(invalidAuthenticationEntryPoint))
                // 前后端分离是无状态的,不需要session了,直接禁用。
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests
                        // 允许所有OPTIONS请求
                        //.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                        // 允许直接访问授权登录接口
                        .requestMatchers(HttpMethod.POST, "/user/login").anonymous() //匿名
                        .requestMatchers(HttpMethod.GET,"/*/hello").permitAll() //允许所有
                        // 允许 SpringMVC 的默认错误地址匿名访问
                        //.requestMatchers("/error").permitAll()
                        // 其他所有接口必须有Authority信息,Authority在登录成功后的UserDetailsImpl对象中默认设置“ROLE_USER”
                        //.requestMatchers("/**").hasAnyAuthority("ROLE_USER")
                        // 允许任意请求被已登录用户访问,不检查Authority
                        .anyRequest().authenticated()) //任意用户认证之后都可以访问
                //.authenticationProvider(authenticationProvider())
                // 加我们自定义的过滤器,替代UsernamePasswordAuthenticationFilter
                .addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
                //配置异常处理器
                .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint) //认证失败
                .accessDeniedHandler(accessDeniedHandler); //授权失败

        return http.build();
    }
}

四、测试

使用postman测试工具

1、使用错误密码登录

2、访问有权限接口

3、访问没有权限的接口

controller:

@PreAuthorize("hasAnyAuthority('test11')")
    @GetMapping("/hello")
    public String hello(){
        return "hello";
    }

  • 11
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值