Geeks_Z の Blog Geeks_Z の Blog
首页
  • 学习笔记

    • 《HTML》
    • 《CSS》
    • 《JavaWeb》
    • 《Vue》
  • 后端文章

    • Linux
    • Maven
    • 汇编语言
    • 软件工程
    • 计算机网络概述
    • Conda
    • Pip
    • Shell
    • SSH
    • Mac快捷键
    • Zotero
  • 学习笔记

    • 《数据结构与算法》
    • 《算法设计与分析》
    • 《Spring》
    • 《SpringMVC》
    • 《SpringBoot》
    • 《SpringCloud》
    • 《Nginx》
  • 深度学习文章
  • 学习笔记

    • 《PyTorch》
    • 《ReinforementLearning》
    • 《MetaLearning》
  • 学习笔记

    • 《高等数学》
    • 《线性代数》
    • 《概率论与数理统计》
  • 增量学习
  • 哈希学习
GitHub (opens new window)

Geeks_Z

AI小学生
首页
  • 学习笔记

    • 《HTML》
    • 《CSS》
    • 《JavaWeb》
    • 《Vue》
  • 后端文章

    • Linux
    • Maven
    • 汇编语言
    • 软件工程
    • 计算机网络概述
    • Conda
    • Pip
    • Shell
    • SSH
    • Mac快捷键
    • Zotero
  • 学习笔记

    • 《数据结构与算法》
    • 《算法设计与分析》
    • 《Spring》
    • 《SpringMVC》
    • 《SpringBoot》
    • 《SpringCloud》
    • 《Nginx》
  • 深度学习文章
  • 学习笔记

    • 《PyTorch》
    • 《ReinforementLearning》
    • 《MetaLearning》
  • 学习笔记

    • 《高等数学》
    • 《线性代数》
    • 《概率论与数理统计》
  • 增量学习
  • 哈希学习
GitHub (opens new window)
  • Linux

  • 数据结构与算法

  • 算法设计与分析

  • Java

  • Python

  • 设计模式

  • 计算机网络

  • Spring笔记

  • SpringMVC笔记

  • SpringBoot笔记

  • SpringSecurity

    • SpringSecurity
      • 身份认证 - 登录认证
      • 身份认证 - jwt认证
      • 自定义UserLoginFailureHandler
      • 自定义UserLoginSuccessHandler
      • JWTTokenUtil
      • 配置SecurityConfig
      • 解决授权
        • 无权限数据返回
    • SpringSecurity简介
    • SpringSecurity Web 权限方案
    • 新建 Markdown
    • 新建 Markdown
    • Handler
    • 注解使用
    • CSRF
    • 访问控制
  • Elasticsearch笔记

  • RabbitMQ笔记

  • Docker笔记

  • MySQL

  • Redis

  • Mybatis

  • MybatisPlus

  • Nginx

  • Kubernetes笔记

  • Git

  • Software

  • 微服务笔记

  • bug

  • BackEndNotes
  • SpringSecurity
Geeks_Z
2023-08-10
目录

SpringSecurity

Spring Security开发配置

Reference

  • https://www.zhuawaba.com/post/19 (opens new window)

security原理 (opens new window)

image-20230810091026005

流程说明:

  1. 客户端发起一个请求,进入 Security 过滤器链。
  2. 当到 LogoutFilter 的时候判断是否是登出路径,如果是登出路径则到 logoutHandler ,如果登出成功则到 logoutSuccessHandler 登出成功处理。如果不是登出路径则直接进入下一个过滤器。
  3. 当到 UsernamePasswordAuthenticationFilter 的时候判断是否为登录路径,如果是,则进入该过滤器进行登录操作,如果登录失败则到 AuthenticationFailureHandler ,登录失败处理器处理,如果登录成功则到 AuthenticationSuccessHandler 登录成功处理器处理,如果不是登录请求则不进入该过滤器。
  4. 进入认证BasicAuthenticationFilter进行用户认证,成功的话会把认证了的结果写入到SecurityContextHolder中SecurityContext的属性authentication上面。如果认证失败就会交给AuthenticationEntryPoint认证失败处理类,或者抛出异常被后续ExceptionTranslationFilter过滤器处理异常,如果是AuthenticationException就交给AuthenticationEntryPoint处理,如果是AccessDeniedException异常则交给AccessDeniedHandler处理。
  5. 当到 FilterSecurityInterceptor 的时候会拿到 uri ,根据 uri 去找对应的鉴权管理器,鉴权管理器做鉴权工作,鉴权成功则到 Controller 层,否则到 AccessDeniedHandler 鉴权失败处理器处理。

Spring Security 实战干货:必须掌握的一些内置 Filter:https://blog.csdn.net/qq_35067322/article/details/102690579 (opens new window)

引入Security与jwt

首先我们导入security包,因为我们前后端交互用户凭证用的是JWT,所以我们也导入jwt的相关包。

  • pom.xml
!-- 权限控制-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-jwt</artifactId>
        <version>1.0.0.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.0</version>
    </dependency>
<!-- hutool工具类--><dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.3.3</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.11</version>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

启动项目,这时候我们再去访问http://localhost:8081 (opens new window),会发现系统会先判断到你未登录跳转到http://localhost:8081/login (opens new window),因为security内置了登录页,用户名为user,密码在启动项目的时候打印在了控制台。登录完成之后我们才可以正常访问接口。因为每次启动密码都会改变,所以我们通过配置文件来配置一下默认的用户名和密码:

  • application.yml
spring:
  security:
    user:
      name: user
      password: 111111
1
2
3
4
5

用户认证

首先我们来解决用户认证问题,分为首次登陆,和二次认证。

  • 首次登录认证:用户名和密码完成登录
  • 二次token认证:请求头携带Jwt进行身份认证

身份认证 - 登录认证

💡 该步骤主要实现从数据库查询用户名和密码,并实现校验

https://image-1300566513.cos.ap-guangzhou.myqcloud.com/upload/images/20210423/2b144e1469d544d8963d461fe5cdec15.png

  • 密码加密

使用Security内置的BCryptPasswordEncoder饼子啊SecurityConfig中进行配置:

/**
     * 加密方式
     *
     * @return
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
1
2
3
4
5
6
7
8
9
  • 重写UserDetailsService接口,实现数据库查询用户数据的过程
/**
 * @ClassName SysUserDetailsService
 * @Description 用户登录Service
 * @Author hwzhao
 * @Data 2022/7/22 17:01
 **/
@Service
public class SysUserDetailsService implements UserDetailsService {

    @Autowired
    private SysUserService sysUserService;

    /**
     * 根据用户名查用户信息
     *
     * @param username 用户名
     * @return 用户详细信息
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUser sysUser = sysUserService.findUserByUserName(username);
        if (sysUser != null){
            SysUserDetails sysUserDetails = new SysUserDetails();
            BeanUtils.copyProperties(sysUser, sysUserDetails);

            Set<GrantedAuthority> authorities = new HashSet<>(); // 角色集合

            //查询用户的角色
            List<SysRole> roleList = sysUserService.findRoleByUserId(sysUserDetails.getId());
            roleList.forEach(role -> {
                authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getRoleName()));
            });

            sysUserDetails.setAuthorities(authorities);

            return sysUserDetails;
        }
        return null;
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

因为security在认证用户身份的时候会调用UserDetailsService.loadUserByUsername()方法,因此我们重写了之后security就可以根据我们的流程去查库获取用户了。然后把UserDetailsServiceImpl注入到*UserAuthenticationProvider*中:

  • cn.edu.buaa.security.UserAuthenticationProvider
/**
 * @ClassName UserAuthenticationProvider
 * @Description 用户登录验证处理类
 * @Author hwzhao
 * @Data 2022/7/23 10:52
 * @Version 1.0
 **/
@Component
@Transactional
public class UserAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    private SysUserDetailsService userDetailsService;

    @Autowired
    private SysUserService sysUserService;

    /**
     * 身份验证
     */
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = (String) authentication.getPrincipal(); // 获取用户名
        String password = (String) authentication.getCredentials(); // 获取密码

        //这里先做的假登陆---处理用户登录逻辑
        String[] userList = username.split("\\|\\|");
        int role = Integer.parseInt(userList[0]);
        username = userList[1];
        SysUserDetails sysUserDetails = new SysUserDetails();
        List<GrantedAuthority> grantedAuthorityList = new ArrayList<>();
        if (role == 0) {
            if (username.equals("root") && password.equals("123456")) {
                sysUserDetails.setId(111111L);
                sysUserDetails.setUsername("root");
                sysUserDetails.setName("超级管理员");
                grantedAuthorityList.add(new GrantedAuthority() {
                    @Override
                    public String getAuthority() {
                        return "ROLE_ROOT";
                    }
                });
            } else {
                throw new BadCredentialsException("用户名或密码错误" + "|" + username);
            }
        }

        if (role == 1) {
            NstrDepartment nstrDepartment = new NstrDepartment();
            QueryWrapper<NstrDepartment> nstrDepartmentQueryWrapper = new QueryWrapper<>();
            nstrDepartmentQueryWrapper.eq("nickname", username);
            NstrDepartment item = nstrDepartment.selectOne(nstrDepartmentQueryWrapper);
            if (item == null) {
                throw new BadCredentialsException("用户名或密码错误" + "|" + username);
            }
            //这里需要检查下密码,先不做。
            if (!password.equals("123456")) {
                throw new BadCredentialsException("用户名或密码错误" + "|" + username);
            }
            sysUserDetails.setId(Long.parseLong(item.getId()));
            sysUserDetails.setUsername(item.getNickname());
            sysUserDetails.setName(item.getDepName());
            grantedAuthorityList.add(new GrantedAuthority() {
                @Override
                public String getAuthority() {
                    return "ROLE_ADMIN";
                }
            });
        }

        if (role == 2) {
            CarsInstitution carsInstitutio = new CarsInstitution();
            QueryWrapper<CarsInstitution> carsInstitutionQueryWrapper = new QueryWrapper<>();
            carsInstitutionQueryWrapper.eq("nickname", username);
            CarsInstitution item = carsInstitutio.selectOne(carsInstitutionQueryWrapper);
            if (item == null) {
                throw new BadCredentialsException("用户名或密码错误" + "|" + username);
            }
            //这里需要检查下密码,先不做。
            if (!password.equals("123456")) {
                throw new BadCredentialsException("用户名或密码错误" + "|" + username);
            }
            sysUserDetails.setId(Long.parseLong(item.getInsId()));
            sysUserDetails.setUsername(item.getNickname());
            sysUserDetails.setName(item.getInsName());
            grantedAuthorityList.add(new GrantedAuthority() {
                @Override
                public String getAuthority() {
                    return "ROLE_USER";
                }
            });
        }

        sysUserDetails.setAuthorities(grantedAuthorityList);
        return new UsernamePasswordAuthenticationToken(sysUserDetails, password, sysUserDetails.getAuthorities());
    }

    /**
     * 支持指定的身份验证
     */
    @Override
    public boolean supports(Class<?> authentication) {
        return true;
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106

然后上面UserDetailsService.loadUserByUsername()默认返回的UserDetails,我们自定义了*SysUserDetails*去重写了UserDetails。

  • cn.edu.buaa.security.entity.SysUserDetails
@Data
public class SysUserDetails extends SysUser implements UserDetails, Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * 用户角色
     */
    private Collection<GrantedAuthority> authorities;

    /**
     * 账号是否过期
     */
    private boolean isAccountNonExpired = false;

    /**
     * 账号是否锁定
     */
    private boolean isAccountNonLocked = false;

    /**
     * 证书是否过期
     */
    private boolean isCredentialsNonExpired = false;

    /**
     * 账号是否有效
     */
    private boolean isEnabled = true;

    /**
     * 获得用户权限
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    /**
     * 判断账号是否过期
     */
    @Override
    public boolean isAccountNonExpired() {
        return isAccountNonExpired;
    }

    /**
     * 判断账号是否锁定
     */
    @Override
    public boolean isAccountNonLocked() {
        return isAccountNonLocked;
    }

    /**
     * 判断证书是否过期
     */
    @Override
    public boolean isCredentialsNonExpired() {
        return isCredentialsNonExpired;
    }

    /**
     * 判断账号是否有效
     */
    @Override
    public boolean isEnabled() {
        return isEnabled;
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70

身份认证 - jwt认证

登录成功之后前端就可以获取到了jwt的信息

service.interceptors.request.use(
  config => {
    // TODO:请求前的处理
    if (getToken()) { // 在请求头中添加token
      config.headers['Authorization'] = getToken()
    }
    return config
  },
  error => {
    // TODO:错误处理
    console.log(error);
    return Promise.reject(error)
  }
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14

所以后端进行用户身份识别的时候,我们需要通过请求头中获取jwt,然后解析出我们的用户名,这样我们就可以知道是谁在访问我们的接口啦,然后判断用户是否有权限等操作。

那么我们自定义一个过滤器用来进行识别jwt。

  • JWTAuthenticationFilter
/**
 * @ClassName JWTAuthenticationFilter
 * @Description JWT权限过滤器,用于验证Token是否合法
 * @Author hwzhao
 * @Data 2022/7/23 17:48
 * @Version 1.0
 **/
public class JWTAuthenticationFilter extends BasicAuthenticationFilter {

    public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws IOException, ServletException {
        // 取出Token
        String token = request.getHeader(JWTConfig.tokenHeader);

        if (token != null && token.startsWith(JWTConfig.tokenPrefix)) {
            SysUserDetails sysUserDetails = JWTTokenUtil.parseAccessToken(token);

            if (sysUserDetails != null) {
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                        sysUserDetails, sysUserDetails.getId(), sysUserDetails.getAuthorities());
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        }
        filterChain.doFilter(request, response);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

上面的逻辑也很简单,正如我前面说到的,获取到用户名之后我们直接把封装成UsernamePasswordAuthenticationToken,之后交给SecurityContextHolder参数传递authentication对象,这样后续security就能获取到当前登录的用户信息了,也就完成了用户认证。【当认证失败的时候会进入AuthenticationEntryPoint,于是我们自定义认证失败返回的数据】

自定义UserLoginFailureHandler

@Component
public class UserLoginFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        ResponseUtils.responseJson(response, ResponseUtils.response(500, "登录失败", exception.getMessage().split("\\|")[0]));
    }
}
1
2
3
4
5
6
7

自定义UserLoginSuccessHandler

/**
 * @ClassName UserLoginSuccessHandler
 * @Description 登录成功处理类
 * @Author hwzhao
 * @Data 2022/7/22 19:00
 **/
@Component
public class UserLoginSuccessHandler implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

        SysUserDetails sysUserDetails = (SysUserDetails) authentication.getPrincipal();
        String token = JWTTokenUtil.createAccessToken(sysUserDetails);
        Map<String, Object> tokenMap = new HashMap<>();
        tokenMap.put("token", token);
        tokenMap.put("id", sysUserDetails.getId());
        GrantedAuthority role = sysUserDetails.getAuthorities().iterator().next();
        tokenMap.put("role", role.getAuthority().replace("ROLE_", ""));

        tokenMap.put("name", sysUserDetails.getName());
        ResponseUtils.responseJson(response, ResponseUtils.response(200, "登录成功", tokenMap));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

JWTTokenUtil

public class JWTTokenUtil {
    /**
     * 创建Token
     *
     * @param sysUserDetails 用户信息
     * @return
     */
    public static String createAccessToken(SysUserDetails sysUserDetails) {
        String token = Jwts.builder().setId(// 设置JWT
                        sysUserDetails.getId().toString()) // 用户Id
                .setSubject(sysUserDetails.getName()) // 主题
                .setIssuedAt(new Date()) // 签发时间
                .setIssuer("zhw") // 签发者
                .setExpiration(new Date(System.currentTimeMillis() + JWTConfig.expiration)) // 过期时间
                .signWith(SignatureAlgorithm.HS512, JWTConfig.secret) // 签名算法、密钥
                .claim("authorities", JSON.toJSONString(sysUserDetails.getAuthorities())).compact(); // 自定义其他属性,如用户组织机构ID,用户所拥有的角色,用户权限信息等
        return JWTConfig.tokenPrefix + token;
    }

    /**
     * 解析Token
     *
     * @param token Token信息
     * @return
     */
    public static SysUserDetails parseAccessToken(String token) {
        System.out.println(token);
        SysUserDetails sysUserDetails = null;
        if (StringUtils.isNotEmpty(token)) {
            try {
                // 去除JWT前缀
                token = token.substring(JWTConfig.tokenPrefix.length());

                // 解析Token
                Claims claims = Jwts.parser().setSigningKey(JWTConfig.secret).parseClaimsJws(token).getBody();

                // 获取用户信息
                sysUserDetails = new SysUserDetails();
                sysUserDetails.setId(Long.parseLong(claims.getId()));
                sysUserDetails.setName(claims.getSubject());
                // 获取角色
                Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
                String authority = claims.get("authorities").toString();
                System.out.println(authority);
                if (StringUtils.isNotEmpty(authority)) {
                    List<Map<String, String>> authorityList = JSON.parseObject(authority,
                            new TypeReference<List<Map<String, String>>>() {
                            });
                    for (Map<String, String> role : authorityList) {
                        if (!role.isEmpty()) {
                            authorities.add(new SimpleGrantedAuthority(role.get("authority")));
                        }
                    }
                }
                sysUserDetails.setAuthorities(authorities);
            } catch (Exception e) {
                e.printStackTrace();
                log.error("解析Token异常:" + e);
            }
        }
        return sysUserDetails;
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

登录成功之后我们利用用户名生成jwt,jwtUtils这个工具类我就不贴代码了哈,去看我们项目源码,然后把jwt作为请求头返回回去,名称就叫Authorization哈。我们需要在配置文件中配置一些jwt的一些密钥信息:

  • application.yml
# JWT配置
jwt:
  # 密匙KEY
  secret: JWTshengou
  # HeaderKEY
  tokenHeader: Authorization
  # Token前缀字符
  tokenPrefix: BJ-
  # 过期时间 单位秒 1天后过期=86400 7天后过期=604800
  expiration: 86400
  # 配置不需要认证的接口
  antMatchers: /login**,/favicon.ico,/user/insert**,/user/userExist**
1
2
3
4
5
6
7
8
9
10
11
12

配置SecurityConfig

  • SecurityConfig
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) // 开启方法权限注解
public class SysSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    MyAuthenticationFilter authenticationFilter() throws Exception {
        MyAuthenticationFilter filter = new MyAuthenticationFilter();
        filter.setAuthenticationManager(authenticationManager());
        return filter;
    }

    /**
     * 无权限处理类
     */
    @Autowired
    private UserAccessDeniedHandler userAccessDeniedHandler;

    /**
     * 用户未登录处理类
     */
    @Autowired
    private UserNotLoginHandler userNotLoginHandler;

    /**
     * 用户登录成功处理类
     */
    @Autowired
    private UserLoginSuccessHandler userLoginSuccessHandler;

    /**
     * 用户登录失败处理类
     */
    @Autowired
    private UserLoginFailureHandler userLoginFailureHandler;

    /**
     * 用户登出成功处理类
     */
    @Autowired
    private UserLogoutSuccessHandler userLogoutSuccessHandler;

    /**
     * 用户登录验证
     */
    @Autowired
    private UserAuthenticationProvider userAuthenticationProvider;

    /**
     * 用户权限注解
     */
    @Autowired
    private UserPermissionEvaluator userPermissionEvaluator;

    /**
     * 加密方式
     *
     * @return
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 注入自定义PermissionEvaluator
     *
     * @return
     */
    @Bean
    public DefaultWebSecurityExpressionHandler userSecurityExpressionHandler() {
        DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
        handler.setPermissionEvaluator(userPermissionEvaluator);
        return handler;
    }

    /**
     * 用户登录验证
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(userAuthenticationProvider);
    }

    /**
     * 安全权限配置
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests() // 权限配置
                .antMatchers(JWTConfig.antMatchers.split(",")).permitAll()// 获取白名单(不进行权限验证)
                .anyRequest().authenticated()// 其他的需要登陆后才能访问
                .and().httpBasic().authenticationEntryPoint(userNotLoginHandler) // 配置未登录处理类
                .and().formLogin().loginProcessingUrl("/login/submit")// 配置登录URL
                .successHandler(userLoginSuccessHandler) // 配置登录成功处理类
                .failureHandler(userLoginFailureHandler) // 配置登录失败处理类
                .and().logout().logoutUrl("/logout/submit")// 配置登出地址
                .logoutSuccessHandler(userLogoutSuccessHandler) // 配置用户登出处理类
                .and().exceptionHandling().accessDeniedHandler(userAccessDeniedHandler)// 配置没有权限处理类
                .and().cors()// 开启跨域
                .and().csrf().disable(); // 禁用跨站请求伪造防护
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); // 禁用session(使用Token认证)
        http.headers().cacheControl(); // 禁用缓存
        http.addFilter(new JWTAuthenticationFilter(authenticationManager())); //// 添加JWT过滤器

    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108

解决授权

然后关于权限部分,也是security的重要功能,当用户认证成功之后,我们就知道谁在访问系统接口,这是又有一个问题,就是这个用户有没有权限来访问我们这个接口呢,要解决这个问题,我们需要知道用户有哪些权限,哪些角色,这样security才能我们做权限判断。

之前我们已经定义及几张表,用户、角色、菜单、以及一些关联表,一般当权限粒度比较细的时候,我们都通过判断用户有没有此菜单或操作的权限,而不是通过角色判断,而用户和菜单是不直接做关联的,是通过用户拥有哪些角色,然后角色拥有哪些菜单权限这样来获得的。

问题1:我们是在哪里赋予用户权限的?有两个地方:

  • 1、用户登录,调用调用UserDetailsService.loadUserByUsername()方法时候可以返回用户的权限信息。
  • 2、接口调用进行身份认证过滤器时候JWTAuthenticationFilter,需要返回用户权限信息

问题2:在哪里决定什么接口需要什么权限?

Security内置的权限注解:

  • @PreAuthorize (opens new window):方法执行前进行权限检查
  • @PostAuthorize (opens new window):方法执行后进行权限检查
  • @Secured (opens new window):类似于 @PreAuthorize (opens new window)

可以在Controller的方法前添加这些注解表示接口需要什么权限。

比如需要Admin角色权限:

@PreAuthorize("hasRole('admin')")
1

比如需要添加管理员的操作权限

@PreAuthorize("hasAuthority('sys:user:save')") //sys:user:save为对应数据库中自己设置的参数权限名称
1

验证权限的流程:

  1. 用户登录或者调用接口时候识别到用户,并获取到用户的权限信息
  2. 注解标识Controller中的方法需要的权限或角色
  3. Security通过FilterSecurityInterceptor匹配URI和权限是否匹配
  4. 有权限则可以访问接口,当无权限的时候返回异常交给AccessDeniedHandler操作类处理
  • SysUserDetailsService
@Service
public class SysUserDetailsService implements UserDetailsService {

    @Autowired
    private SysUserService sysUserService;

    /**
     * 根据用户名查用户信息
     *
     * @param username 用户名
     * @return 用户详细信息
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUser sysUser = sysUserService.findUserByUserName(username);
        if (sysUser != null){
            SysUserDetails sysUserDetails = new SysUserDetails();
            BeanUtils.copyProperties(sysUser, sysUserDetails);

            Set<GrantedAuthority> authorities = new HashSet<>(); // 角色集合

            //查询用户的角色
            List<SysRole> roleList = sysUserService.findRoleByUserId(sysUserDetails.getId());
            roleList.forEach(role -> {
                authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getRoleName()));
            });

            sysUserDetails.setAuthorities(authorities);

            return sysUserDetails;
        }
        return null;
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
  • JWTAuthenticationFilter
/**
 * @ClassName JWTAuthenticationFilter
 * @Description JWT权限过滤器,用于验证Token是否合法
 * @Author hwzhao
 * @Data 2022/7/23 17:48
 * @Version 1.0
 **/
public class JWTAuthenticationFilter extends BasicAuthenticationFilter {

    public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws IOException, ServletException {
        // 取出Token
        String token = request.getHeader(JWTConfig.tokenHeader);

        if (token != null && token.startsWith(JWTConfig.tokenPrefix)) {
            SysUserDetails sysUserDetails = JWTTokenUtil.parseAccessToken(token);

            if (sysUserDetails != null) {
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                        sysUserDetails, sysUserDetails.getId(), sysUserDetails.getAuthorities());
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        }
        filterChain.doFilter(request, response);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

然后sysUserService.findRoleByUserId(sysUserDetails.getId());因为要查询数据库,具体SQL如下:

<!-- 根据用户ID查询角色 -->
    <select id="findRoleByUserId" resultType="cn.edu.buaa.entity.SysRole" parameterType="long">
        SELECT r.*
        FROM sys_role r
                 LEFT JOIN sys_user_role ur ON ur.role_id = r.id
        WHERE ur.user_id = #{userId}
    </select>

    <!-- 根据用户ID查询权限 -->
    <select id="findAuthByUserId" resultType="cn.edu.buaa.entity.SysAuth" parameterType="long">
        SELECT a.*
        FROM sys_auth a
                 LEFT JOIN sys_role_auth ra ON ra.auth_id = a.id
                 LEFT JOIN sys_user_role ur ON ur.role_id = ra.role_id
        WHERE ur.user_id = #{userId}
    </select>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

无权限数据返回

  • com.markerhub.security.JwtAccessDeniedHandler
/**
 * @ClassName UserAccessDeniedHandler
 * @Description 无权限处理类
 * @Author hwzhao
 * @Data 2022/7/23 22:39
 **/
@Component
public class UserAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response,
                       AccessDeniedException accessDeniedException) throws IOException, ServletException {

//        //写入登陆时间
//        LoginRecord loginRecord = new LoginRecord();
//        loginRecord.setUsername(accessDeniedException.getMessage().split("\\|")[1]);
//        loginRecord.setLoginType("登陆");
//        loginRecord.setLoginStatus("拒绝访问");
//        loginRecord.setCreateTime(LocalDateTime.now(Clock.system(ZoneId.of("Asia/Shanghai"))).toString());
//        loginRecord.insert();
        ResponseUtils.responseJson(response, ResponseUtils.response(403, "拒绝访问", accessDeniedException.getMessage().split("\\|")[0]));
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

跨域

/**
 * @ClassName CorsConfig
 * @Description AJAX请求跨域
 * @Author hwzhao
 * @Data 2022/7/23 16:53
 * @Version 1.0
 **/
@Configuration
public class CorsConfig extends WebMvcConfigurerAdapter {

    private CorsConfiguration buildConfig() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        corsConfiguration.addExposedHeader("Authorization");
        return corsConfiguration;
    }

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", buildConfig());
        return new CorsFilter(source);
    }

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .exposedHeaders("Authorization")
                .allowedOrigins("*")
                .allowCredentials(true)
                .allowedMethods("GET", "POST", "DELETE", "PUT", "OPTIONS")
                .allowedHeaders("*")
                .exposedHeaders("access-control-allow-headers",
                        "access-control-allow-methods",
                        "access-control-allow-origin",
                        "access-control-max-age",
                        "X-Frame-Options")
                .maxAge(3600);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
上次更新: 2024/03/29, 08:56:31
SpringBoot 部署打包成 jar 和 war 有什么不同
SpringSecurity简介

← SpringBoot 部署打包成 jar 和 war 有什么不同 SpringSecurity简介→

最近更新
01
并行训练
03-29
02
tensor维度转换
03-26
03
ResNet源码解读
03-23
更多文章>
Theme by Vdoing | Copyright © 2022-2024 Geeks_Z | MIT License
京公网安备 11010802040735号 | 京ICP备2022029989号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式