Sping Security前后端分离两种实战方案

 更新时间:2023年03月27日 08:21:19   作者:"大魔王先生  
这篇文章主要介绍了Sping Security前后端分离两种方案,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

Java技术迷

前言

本篇文章是基于Spring Security实现前后端分离登录认证及权限控制的实战,主要包括以下四方面内容:

  • Spring Seciruty简单介绍;

  • 通过Spring Seciruty实现的基于表单和Token认证的两种认证方式;

  • 自定义实现RBAC的权限控制;

  • 跨域问题处理;

Spring Seciruty简单介绍

Spring Security是基于Spring框架,提供了一套Web应用安全性的完整解决方案。关于安全方面的两个核心功能是认证和授权,Spring Security重要核心功能就是实现用户认证(Authentication)和用户授权(Authorization)。

认证(Authentication)

认证是用来验证某个用户能否访问该系统。用户认证一般要求用户提供用户名和密码,系统通过校验用户名和密码来完成认证过程。

授权(Authorization)

授权发生在认证之后,用来验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。

实现简单介绍

Spring Security进行认证和鉴权的时候,采用的一系列的Filter来进行拦截的。 下图是Spring Security基于表单认证授权的流程,

在Spring Security一个请求想要访问到API就会从左到右经过蓝线框里的过滤器,其中绿色部分是负责认证的过滤器,蓝色部分是负责异常处理,橙色部分则是负责授权。进过一系列拦截最终访问到我们的API。

准备阶段

整个项目结构如下,demo1部分是基于表单的认证,demo2部分是基于Token的认证,数据库采用是Mysql,访问数据库使用的JPA,Spring Boot版本是2.7.8,Spring Security版本是比较新的5.7.6,这里需要注意的是Spring Security5.7以后版本和前面的版本有一些差异,未来该Demo的版本的问题一直会持续保持升级。 后续也会引用前端项目,前端后台管理部分我个人感觉后端程序员也要进行简单的掌握一些,便于工作中遇到形形色色问题更好的去处理。

Maven

关于Maven部分细节这里不进行展示了,采用的父子工程,主要简单看下依赖的版本,

1
2
3
4
5
6
7
8
9
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <springboot.vetsion>2.7.8</springboot.vetsion>
        <mysql-connector-java.version>5.1.46</mysql-connector-java.version>
        <org.projectlombok.version>1.16.14</org.projectlombok.version>
        <jjwt.version>0.11.1</jjwt.version>
        <fastjson.version>1.2.75</fastjson.version>
    </properties>

统一错误码

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
public enum ResultCode {
 
    /* 成功 */
    SUCCESS(200"成功"),
 
    /* 默认失败 */
    COMMON_FAIL(999"失败"),
 
    /* 参数错误:1000~1999 */
    PARAM_NOT_VALID(1001"参数无效"),
    PARAM_IS_BLANK(1002"参数为空"),
    PARAM_TYPE_ERROR(1003"参数类型错误"),
    PARAM_NOT_COMPLETE(1004"参数缺失"),
 
    /* 用户错误 */
    USER_NOT_LOGIN(2001"用户未登录"),
    USER_ACCOUNT_EXPIRED(2002"账号已过期"),
    USER_CREDENTIALS_ERROR(2003"密码错误"),
    USER_CREDENTIALS_EXPIRED(2004"密码过期"),
    USER_ACCOUNT_DISABLE(2005"账号不可用"),
    USER_ACCOUNT_LOCKED(2006"账号被锁定"),
    USER_ACCOUNT_NOT_EXIST(2007"账号不存在"),
    USER_ACCOUNT_ALREADY_EXIST(2008"账号已存在"),
    USER_ACCOUNT_USE_BY_OTHERS(2009"账号下线"),
 
    /* 业务错误 */
    NO_PERMISSION(3001"没有权限");
    private Integer code;
    private String message;
 
    ResultCode(Integer code, String message) {
        this.code = code;
        this.message = message;
    }
 
    public Integer getCode() {
        return code;
    }
 
    public void setCode(Integer code) {
        this.code = code;
    }
 
    public String getMessage() {
        return message;
    }
 
    public void setMessage(String message) {
        this.message = message;
    }
 
    private static Map<Integer, ResultCode> map = new HashMap<>();
    private static Map<String, ResultCode> descMap = new HashMap<>();
 
 
    static {
        for (ResultCode value : ResultCode.values()) {
            map.put(value.getCode(), value);
            descMap.put(value.getMessage(), value);
        }
    }
 
    public static ResultCode getByCode(Integer code) {
        return map.get(code);
    }
 
    public static ResultCode getByMessage(String desc) {
        return descMap.get(desc);
    }
}

统一返回定义

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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
public class CommonResponse<T> implements Serializable {
 
    /**
     * 成功状态码
     */
    private final static String SUCCESS_CODE = "SUCCESS";
 
    /**
     * 提示信息
     */
    private String message;
 
    /**
     * 返回数据
     */
    private T data;
 
    /**
     * 状态码
     */
    private Integer code;
 
    /**
     * 状态
     */
    private Boolean state;
 
    /**
     * 错误明细
     */
    private String detailMessage;
 
 
    /**
     * 成功
     *
     * @param <T> 泛型
     * @return 返回结果
     */
    public static <T> CommonResponse<T> ok() {
        return ok(null);
    }
 
    /**
     * 成功
     *
     * @param data 传入的对象
     * @param <T>  泛型
     * @return 返回结果
     */
    public static <T> CommonResponse<T> ok(T data) {
        CommonResponse<T> response = new CommonResponse<T>();
        response.code = ResultCode.SUCCESS.getCode();
        response.data = data;
        response.message = "返回成功";
        response.state = true;
        return response;
    }
 
    /**
     * 错误
     *
     * @param code    自定义code
     * @param message 自定义返回信息
     * @param <T>     泛型
     * @return 返回信息
     */
    public static <T> CommonResponse<T> error(Integer code, String message) {
        return error(code, message, null);
    }
 
    /**
     * 错误
     *
     * @param code          自定义code
     * @param message       自定义返回信息
     * @param detailMessage 错误详情信息
     * @param <T>           泛型
     * @return 返回错误信息
     */
    public static <T> CommonResponse<T> error(Integer code, String message,
                                              String detailMessage) {
        CommonResponse<T> response = new CommonResponse<T>();
        response.code = code;
        response.data = null;
        response.message = message;
        response.state = false;
        response.detailMessage = detailMessage;
        return response;
    }
 
    public Boolean getState() {
        return state;
    }
 
    public CommonResponse<T> setState(Boolean state) {
        this.state = state;
        return this;
    }
 
    public String getMessage() {
        return message;
    }
 
    public CommonResponse<T> setMessage(String message) {
        this.message = message;
        return this;
    }
 
    public T getData() {
        return data;
    }
 
    public CommonResponse<T> setData(T data) {
        this.data = data;
        return this;
    }
 
    public Integer getCode() {
        return code;
    }
 
    public CommonResponse<T> setCode(Integer code) {
        this.code = code;
        return this;
    }
 
    public String getDetailMessage() {
        return detailMessage;
    }
 
    public CommonResponse<T> setDetailMessage(String detailMessage) {
        this.detailMessage = detailMessage;
        return this;
    }
}

数据库设计

基于RBAC模型最简单奔版本的数据库设计,用户、角色、权限表;

基于表单认证

对于表单认证整体过程可以参考下图,

核心配置

核心配置包含了框架开启以及权限配置,这部分内容是重点要关注的,这里可以看到所有重写的内容,主要包含以下七方面内容:

  • 定义哪些资源不需要认证,哪些需要认证,这里我采用注解形式;

  • 实现自定义认证以及授权异常的接口;

  • 实现自定义登录成功以及失败的接口;

  • 实现自定义登出以后的接口;

  • 实现自定义重数据查询对应的账号权限的接口;

  • 自定义加密的Bean;

  • 自定义授权认证Bean;

当然Spring Security还支持更多内容,比如限制用户登录个数等等,这里部分内容使用不是太多,后续大家如果有需要我也可以进行补充。

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
//Spring Security框架开启
@EnableWebSecurity
//授权全局配置
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
public class SecurityConfig {
 
    @Autowired
    private SysUserService sysUserService;
 
    @Autowired
    private NotAuthenticationConfig notAuthenticationConfig;
 
 
    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        //支持跨域
        http.cors().and()
                //csrf关闭
                .csrf().disable()
                //配置哪些需要认证 哪些不需要认证
                .authorizeRequests(rep -> rep.antMatchers(notAuthenticationConfig.getPermitAllUrls().toArray(new String[0]))
                        .permitAll().anyRequest().authenticated())
                .exceptionHandling()
                //认证异常处理
                .authenticationEntryPoint(new ResourceAuthExceptionEntryPoint())
                //授权异常处理
                .accessDeniedHandler(new CustomizeAccessDeniedHandler())
                //登录认证处理
                .and().formLogin()
                .successHandler(new CustomizeAuthenticationSuccessHandler())
                .failureHandler(new CustomizeAuthenticationFailureHandler())
                //登出
                .and().logout().permitAll().addLogoutHandler(new CustomizeLogoutHandler())
                .logoutSuccessHandler(new CustomizeLogoutSuccessHandler())
                .deleteCookies("JSESSIONID")
                //自定义认证
                .and().userDetailsService(sysUserService);
        return http.build();
    }
 
 
    @Bean
    public PasswordEncoder passwordEncoder() {
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        return bCryptPasswordEncoder;
    }
 
    @Bean("ssc")
    public SecuritySecurityCheckService permissionService() {
        return new SecuritySecurityCheckService();
    }
     
}

通过注解形式实现哪些需要资源不需要认证

通过自定义注解@NotAuthentication,然后通过实现InitializingBean接口,实现加载不需要认证的资源,支持类和方法,使用就是通过在方法或者类打上对应的注解。

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
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })
public @interface NotAuthentication {
}
 
@Service
public class NotAuthenticationConfig implements InitializingBean, ApplicationContextAware {
 
    private static final String PATTERN = "\\{(.*?)}";
 
    public static final String ASTERISK = "*";
 
 
    private ApplicationContext applicationContext;
 
    @Getter
    @Setter
    private List<String> permitAllUrls = new ArrayList<>();
 
    @Override
    public void afterPropertiesSet() throws Exception {
        RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
        Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
        map.keySet().forEach(x -> {
            HandlerMethod handlerMethod = map.get(x);
 
            // 获取方法上边的注解 替代path variable 为 *
            NotAuthentication method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), NotAuthentication.class);
            Optional.ofNullable(method).ifPresent(inner -> Objects.requireNonNull(x.getPathPatternsCondition())
                    .getPatternValues().forEach(url -> permitAllUrls.add(url.replaceAll(PATTERN, ASTERISK))));
 
            // 获取类上边的注解, 替代path variable 为 *
            NotAuthentication controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), NotAuthentication.class);
            Optional.ofNullable(controller).ifPresent(inner -> Objects.requireNonNull(x.getPathPatternsCondition())
                    .getPatternValues().forEach(url -> permitAllUrls.add(url.replaceAll(PATTERN, ASTERISK))));
        });
    }
 
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

自定义认证异常实现

AuthenticationEntryPoint�用来解决匿名用户访问无权限资源时的异常。

1
2
3
4
5
6
7
8
9
10
public class ResourceAuthExceptionEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        CommonResponse result= CommonResponse.error(ResultCode.USER_NOT_LOGIN.getCode(),
                ResultCode.USER_NOT_LOGIN.getMessage());
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        response.getWriter().write(JSON.toJSONString(result));
    }
}

自定义授权异常实现

AccessDeniedHandler用来解决认证过的用户访问无权限资源时的异常。

1
2
3
4
5
6
7
8
9
10
11
public class CustomizeAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        CommonResponse result = CommonResponse.error(ResultCode.NO_PERMISSION.getCode(),
                        ResultCode.NO_PERMISSION.getMessage());
        //处理编码方式,防止中文乱码的情况
        response.setContentType("text/json;charset=utf-8");
        //塞到HttpServletResponse中返回给前台
        response.getWriter().write(JSON.toJSONString(result));
    }
}

自定义登录成功、失败

AuthenticationSuccessHandler和AuthenticationFailureHandler这两个接口用于登录成功失败以后的处理。

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
public class CustomizeAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        AuthUser authUser = (AuthUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        //返回json数据
        CommonResponse<AuthUser> result = CommonResponse.ok(authUser);
        //处理编码方式,防止中文乱码的情况
        response.setContentType("text/json;charset=utf-8");
        //塞到HttpServletResponse中返回给前台
        response.getWriter().write(JSON.toJSONString(result));
    }
}
 
public class CustomizeAuthenticationFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        //返回json数据
        CommonResponse result = null;
        if (exception instanceof AccountExpiredException) {
            //账号过期
            result = CommonResponse.error(ResultCode.USER_ACCOUNT_EXPIRED.getCode(), ResultCode.USER_ACCOUNT_EXPIRED.getMessage());
        else if (exception instanceof BadCredentialsException) {
            //密码错误
            result = CommonResponse.error(ResultCode.USER_CREDENTIALS_ERROR.getCode(), ResultCode.USER_CREDENTIALS_ERROR.getMessage());
//        } else if (exception instanceof CredentialsExpiredException) {
//            //密码过期
//            result = CommonResponse.error(ResultCode.USER_CREDENTIALS_EXPIRED);
//        } else if (exception instanceof DisabledException) {
//            //账号不可用
//            result = CommonResponse.error(ResultCode.USER_ACCOUNT_DISABLE);
//        } else if (exception instanceof LockedException) {
//            //账号锁定
//            result = CommonResponse.error(ResultCode.USER_ACCOUNT_LOCKED);
//        } else if (exception instanceof InternalAuthenticationServiceException) {
//            //用户不存在
//            result = CommonResponse.error(ResultCode.USER_ACCOUNT_NOT_EXIST);
        else {
            //其他错误
            result = CommonResponse.error(ResultCode.COMMON_FAIL.getCode(), ResultCode.COMMON_FAIL.getMessage());
        }
        //处理编码方式,防止中文乱码的情况
        response.setContentType("text/json;charset=utf-8");
        //塞到HttpServletResponse中返回给前台
        response.getWriter().write(JSON.toJSONString(result));
    }
}

自定义登出

LogoutHandler自定义登出以后处理逻辑,比如记录在线时长等等;LogoutSuccessHandler登出成功以后逻辑处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class CustomizeLogoutSuccessHandler implements LogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
 
        CommonResponse result = CommonResponse.ok();
        response.setContentType("text/json;charset=utf-8");
        response.getWriter().write(JSON.toJSONString(result));
    }
}
 
public class CustomizeLogoutHandler implements LogoutHandler {
 
    @Override
    public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
 
    }
}

自定义认证

自定义认证涉及三个对象UserDetialsService、UserDetails以及PasswordEncoder,整个流程首先根据用户名查询出用户对象交由UserDetialsService接口处理,该接口只有一个方法loadUserByUsername,通过用户名查询用户对象。查询出来的用户对象需要通过Spring Security中的用户数据UserDetails实体类来体现,这里使用AuthUser继承User,User本质上就是继承与UserDetails,UserDetails该类中提供了账号、密码等通用属性。对密码进行校验使用PasswordEncoder组件,负责密码加密与校验。

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
public class AuthUser extends User {
 
    public AuthUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {
        super(username, password, authorities);
    }
}
 
@Service
public class SysUserService implements UserDetailsService {
 
    @Autowired
    private SysUserRepository sysUserRepository;
 
    @Autowired
    private SysRoleService sysRoleService;
 
    @Autowired
    private SysMenuService sysMenuService;
 
 
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Optional<SysUser> sysUser = Optional.ofNullable(sysUserRepository.findOptionalByUsername(username).orElseThrow(() ->
                new UsernameNotFoundException("未找到用户名")));
        List<SysRole> roles = sysRoleService.queryByUserName(sysUser.get().getUsername());
        Set<String> dbAuthsSet = new HashSet<>();
        if (!CollectionUtils.isEmpty(roles)) {
            //角色
            roles.forEach(x -> {
                dbAuthsSet.add("ROLE_" + x.getName());
            });
            List<Long> roleIds = roles.stream().map(SysRole::getId).collect(Collectors.toList());
            List<SysMenu> menus = sysMenuService.queryByRoleIds(roleIds);
            //菜单
            Set<String> permissions= menus.stream().filter(x->x.getType().equals(3))
                    .map(SysMenu::getPermission).collect(Collectors.toSet());
            dbAuthsSet.addAll(permissions);
        }
        Collection<GrantedAuthority> authorities = AuthorityUtils
                .createAuthorityList(dbAuthsSet.toArray(new String[0]));
        return new AuthUser(username, sysUser.get().getPassword(), authorities);
    }
}

基于Token认证

基于Token认证这里我采用JWT方式,下图是整个处理的流程,通过自定义的登录以及JwtAuthenticationTokenFilter来完成整个任务的实现,需要注意的是这里我没有使用缓存。

核心配置

与表单认证不同的是这里关闭和FormLogin表单认证以及不使用Session方式,增加了JwtAuthenticationTokenFilter,此外ResourceAuthExceptionEntryPoint兼职处理之前登录失败以后的异常处理。

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
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
public class SecurityConfig {
 
    @Autowired
    private SysUserService sysUserService;
 
    @Autowired
    private NotAuthenticationConfig notAuthenticationConfig;
    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        //支持跨域
        http.cors().and()
                //csrf关闭
                .csrf().disable()
                //不使用session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and().authorizeRequests(rep -> rep.antMatchers(notAuthenticationConfig.getPermitAllUrls().toArray(new String[0]))
                        .permitAll().anyRequest().authenticated())
                .exceptionHandling()
                //异常认证
                .authenticationEntryPoint(new ResourceAuthExceptionEntryPoint())
                .accessDeniedHandler(new CustomizeAccessDeniedHandler())
                .and()
                //token过滤
                .addFilterBefore(new JwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class)
                .userDetailsService(sysUserService);
        return http.build();
    }
 
 
    /**
     * 获取AuthenticationManager
     *
     * @param configuration
     * @return
     * @throws Exception
     */
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
        return configuration.getAuthenticationManager();
    }
 
    /**
     * 密码
     *
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        return bCryptPasswordEncoder;
    }
 
    @Bean("ssc")
    public SecuritySecurityCheckService permissionService() {
        return new SecuritySecurityCheckService();
    }
 
}

Token创建

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
@Service
public class LoginService {
 
 
    @Autowired
    private AuthenticationManager authenticationManager ;
 
    @Autowired
    private SysUserService sysUserService;
 
 
    public LoginVO login(LoginDTO loginDTO) {
        //创建Authentication对象
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(loginDTO.getUsername(),
                loginDTO.getPassword());
 
        //调用AuthenticationManager的authenticate方法进行认证
        Authentication authentication = authenticationManager.authenticate(authenticationToken);
 
        if(authentication == null) {
            throw new RuntimeException("用户名或密码错误");
        }
        //登录成功以后用户信息、
        AuthUser authUser =(AuthUser)authentication.getPrincipal();
 
        LoginVO loginVO=new LoginVO();
        loginVO.setUserName(authUser.getUsername());
        loginVO.setAccessToken(JwtUtils.createAccessToken(authUser));
        loginVO.setRefreshToken(JwtUtils.createRefreshToken(authUser));
 
        return loginVO;
    }
 
 
    public LoginVO refreshToken(String accessToken, String refreshToken){
        if (!JwtUtils.validateRefreshToken(refreshToken) && !JwtUtils.validateWithoutExpiration(accessToken)) {
            throw new RuntimeException("认证失败");
        }
        Optional<String> userName = JwtUtils.parseRefreshTokenClaims(refreshToken).map(Claims::getSubject);
        if (userName.isPresent()){
            AuthUser authUser = sysUserService.loadUserByUsername(userName.get());
            if (Objects.nonNull(authUser)) {
                LoginVO loginVO=new LoginVO();
                loginVO.setUserName(authUser.getUsername());
                loginVO.setAccessToken(JwtUtils.createAccessToken(authUser));
                loginVO.setRefreshToken(JwtUtils.createRefreshToken(authUser));
                return loginVO;
            }
            throw new InternalAuthenticationServiceException("用户不存在");
        }
        throw new RuntimeException("认证失败");
    }
}

Token过滤

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
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        //check Token
        if (checkJWTToken(request)) {
            //解析token中的认证信息
            Optional<Claims> claimsOptional = validateToken(request)
                    .filter(claims -> claims.get("authorities") != null);
            if (claimsOptional.isPresent()) {
                List<String> authoritiesList = castList(claimsOptional.get().get("authorities"), String.class);
                List<SimpleGrantedAuthority> authorities = authoritiesList
                        .stream().map(String::valueOf)
                        .map(SimpleGrantedAuthority::new).collect(Collectors.toList());
                UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
                        new UsernamePasswordAuthenticationToken(claimsOptional.get().getSubject(), null, authorities);
                SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
            else {
                SecurityContextHolder.clearContext();
            }
        }
        chain.doFilter(request, response);
    }
 
    public static <T> List<T> castList(Object obj, Class<T> clazz) {
        List<T> result = new ArrayList<T>();
        if (obj instanceof List<?>) {
            for (Object o : (List<?>) obj) {
                result.add(clazz.cast(o));
            }
            return result;
        }
        return null;
    }
 
    private Optional<Claims> validateToken(HttpServletRequest req) {
        String jwtToken = req.getHeader("token");
        try {
            return JwtUtils.parseAccessTokenClaims(jwtToken);
        catch (ExpiredJwtException | SignatureException | MalformedJwtException | UnsupportedJwtException | IllegalArgumentException e) {
            //输出日志
            return Optional.empty();
        }
    }
 
    private boolean checkJWTToken(HttpServletRequest request) {
        String authenticationHeader = request.getHeader("token");
        return authenticationHeader != null;
    }
}

授权处理

全局授权的配置已经在核心配置中开启,核心思路是通过SecurityContextHolder获取当前用户权限,判断当前用户的权限是否包含该方法的权限,此部分设计后续如果存在性能问题,可以设计缓存来解决。

授权检查

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
public class SecuritySecurityCheckService {
    public boolean hasPermission(String permission) {
        return hasAnyPermissions(permission);
    }
 
    public boolean hasAnyPermissions(String... permissions) {
        if (CollectionUtils.isEmpty(Arrays.asList(permissions))) {
            return false;
        }
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication == null) {
            return false;
        }
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        return authorities.stream().map(GrantedAuthority::getAuthority).filter(x -> !x.contains("ROLE_"))
                .anyMatch(x -> PatternMatchUtils.simpleMatch(permissions, x));
    }
 
    public boolean hasRole(String role) {
        return hasAnyRoles(role);
    }
 
    public boolean hasAnyRoles(String... roles) {
        if (CollectionUtils.isEmpty(Arrays.asList(roles))) {
            return false;
        }
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication == null) {
            return false;
        }
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        return authorities.stream().map(GrantedAuthority::getAuthority).filter(x -> x.contains("ROLE_"))
                .anyMatch(x -> PatternMatchUtils.simpleMatch(roles, x));
    }
}

如何使用

1
2
3
4
5
@PreAuthorize("@ssc.hasPermission('sys:user:query')")
@PostMapping("/helloWord")
public String hellWord(){
  return "hello word";
}

跨域问题处理

关于这部分跨域部分的配置还可以更加细化一点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // 设置允许跨域的路径
        registry.addMapping("/**")
                // 设置允许跨域请求的域名
                .allowedOriginPatterns("*")
                // 是否允许cookie
                .allowCredentials(true)
                // 设置允许的请求方式
                .allowedMethods("GET""POST""DELETE""PUT")
                // 设置允许的header属性
                .allowedHeaders("*")
                // 跨域允许时间
                .maxAge(3600);
    }
}

vue-admin-template登录的简单探索感悟

这部分就是有些感悟(背景自身是没有接触过Vue相关的知识),具体的感悟就是不要畏惧一些自己不知道以及不会的东西,大胆的去尝试,因为自身的潜力是很大的。为什么要这么讲,通过自己折腾3个小时,自己完成整个登录过程,如果是前端可能会比较简单,针对我这种从没接触过的还是有些难度的,需要一些基础配置更改以及流程梳理,这里简单来让大家看下效果,后续我也会自己把剩下菜单动态加载以及一些简单表单交互来完成,做到简单的表单可以自己来实现。

到此这篇关于Sping Security前后端分离两种方案的文章就介绍到这了,更多相关Sping Security前后端分离内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

蓄力AI

微信公众号搜索 “ 脚本之家 ” ,选择关注

程序猿的那些事、送书等活动等着你

原文链接:https://www.cnblogs.com/wtzbk/p/17260243.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 reterry123@163.com 进行投诉反馈,一经查实,立即处理!

相关文章

  • 使用Java和WebSocket实现网页聊天室实例代码

    使用Java和WebSocket实现网页聊天室实例代码

    WebSocket是HTML5一种新的协议,它实现了浏览器与服务器全双工通信,这里就将使用WebSocket来开发网页聊天室,对Java和WebSocket实现网页聊天室的实例代码感兴趣的朋友一起学习吧
    2016-06-06
  • SpringBoot中@Value获取值和@ConfigurationProperties获取值用法及比较

    SpringBoot中@Value获取值和@ConfigurationProperties获取值用法及比较

    在Spring Boot中,@Value注解是一个非常有用的特性,它允许我们将外部的配置注入到我们的Bean中,@ConfigurationProperties用于将配置文件中的属性绑定到 Java Bean 上,本文介绍了@Value获取值和@ConfigurationProperties获取值用法及比较,需要的朋友可以参考下
    2024-08-08
  • springboot中一些比较常用的注解总结

    springboot中一些比较常用的注解总结

    今天给大家带来的是关于Java的相关知识,文章围绕着springboot中一些比较常用的注解展开,文中有非常详细的总结,需要的朋友可以参考下
    2021-06-06
  • JAVA使用geotools读取shape格式文件的方法

    JAVA使用geotools读取shape格式文件的方法

    这篇文章主要介绍了JAVA使用geotools读取shape格式文件的方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2017-01-01
  • Java基于Swing和netty实现仿QQ界面聊天小项目

    Java基于Swing和netty实现仿QQ界面聊天小项目

    这篇文章主要为大家详细介绍了Java如何利用Swing和netty实现仿QQ界面聊天小项目,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一下
    2022-09-09
  • Java调用python的方法(jython)

    Java调用python的方法(jython)

    这篇文章主要介绍了Java调用python的方法(jython),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-06-06
  • Java实现的分页工具类与用法示例

    Java实现的分页工具类与用法示例

    这篇文章主要介绍了Java实现的分页工具类与用法,结合完整实例形式分析了java分页工具类的定义、使用方法及相关操作技巧,需要的朋友可以参考下
    2019-10-10
  • springboot日期转换器实现实例解析

    springboot日期转换器实现实例解析

    这篇文章主要介绍了springboot日期转换器实现实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-12-12
  • 一篇文章带你入门java网络编程

    一篇文章带你入门java网络编程

    网络编程是指编写运行在多个设备(计算机)的程序,这些设备都通过网络连接起来。本文介绍了一些网络编程基础的概念,并用Java来实现TCP和UDP的Socket的编程,来让读者更好的了解其原理
    2021-08-08
  • java实现excel导出合并单元格的步骤详解

    java实现excel导出合并单元格的步骤详解

    这篇文章主要介绍了java实现excel导出合并单元格,通过使用Apache POI库,我们可以方便地创建Excel文件、填充数据、合并单元格和导出Excel文件,需要的朋友可以参考下
    2023-04-04

最新评论