Springboot+SpringSecurity 权限管理笔记

Springboot+SpringSecurity 权限管理笔记

一、导入Springsecurity 依赖

   <!--security 权限框架-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

二、配置白名单路径

(一)在 application.yml 中配置放行的路径

application.yml

secure:
  ignored:
    urls: #安全路径白名单
      - /swagger-ui.html
      - /swagger-ui/**
      - /swagger-resources/**
      - /swagger/**
      - /v3/**
      - /**/*.js
      - /**/*.css
      - /**/*.png
      - /**/*.ico
      - /webjars/**
      - /actuator/**
      - /druid/**
      - /**/common/**
      - /common/**

(二)IgnoreUrlsConfig 类——获取放行路径

IgnoreUrlsConfig

package com.cloud.wl.hp.config.security;

import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;

import java.util.ArrayList;
import java.util.List;

/**
 * SpringSecurity白名单资源路径配置
 *
 * @author wanglin
 * @version 1.0
 * @date 2022-02-28 周一
 */
@Getter
@Setter
@ConfigurationProperties(prefix = "secure.ignored")
public class IgnoreUrlsConfig {
    private List<String> urls = new ArrayList<>();
}

三、编写 SecurityConfig 配置类

(一)SecurityConfig 配置类

package com.cloud.wl.hp.config.security;

import com.cloud.wl.hp.config.component.JwtAccessDeniedHandler;
import com.cloud.wl.hp.config.component.JwtAuthenticationEntryPoint;
import com.cloud.wl.hp.config.component.JwtAuthenticationTokenFilter;
import com.cloud.wl.hp.config.model.LoginUserDetails;
import com.cloud.wl.hp.domain.SysUser;
import com.cloud.wl.hp.service.CommonService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

/**
 * 对SpringSecurity配置类的扩展,支持自定义白名单资源路径和查询用户逻辑
 *
 * @author wanglin
 * @version 1.0
 * @date 2022-01-14 周五
 * @description
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private CommonService commonService;

    @Autowired
    private JwtAccessDeniedHandler jwtAccessDeniedHandler;

    @Autowired
    private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

    @Override
    @Bean
    public UserDetailsService userDetailsService() {
        return username -> {
            SysUser user = commonService.getLoginUserInfo(username);
            Set<SimpleGrantedAuthority> authorities = new HashSet<>();
            authorities.add(new SimpleGrantedAuthority("admin"));
            LoginUserDetails userDetails = new LoginUserDetails(user, authorities);

            if (Objects.nonNull(user)) {
                return userDetails;
            }
            return null;
        };
    }

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

    @Bean
    public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter() {
        return new JwtAuthenticationTokenFilter();
    }

    /**
     * 实现自己的登录逻辑
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService()).passwordEncoder(bCryptPasswordEncoder());
    }

    //方式二:
//    @Override
//    public void configure(WebSecurity web) {
        放行静态资源
//        web.ignoring()
//                .antMatchers(HttpMethod.GET,
//                        "/swagger-resources/**",
//                        "/common/**",
//                        "/component/**",
//                        "/**/*.html",
//                        "/**/*.css",
//                        "/**/*.js",
//                        "/swagger-ui.html",
//                        "/webjars/**",
//                        "/v3/**",
//                        "/swagger/**");
//    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.authorizeRequests();
        // 不需要保护的资源路径允许访问
        for (String url : ignoreUrlsConfig().getUrls()) {
            registry.antMatchers(url).permitAll();
        }

        registry.and()
//        http
                // 禁用 CSRF
                .csrf().disable()
//                .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
                .authorizeRequests()
                // 允许跨域的OPTIONS请求
                .antMatchers(HttpMethod.OPTIONS).permitAll()
                // 放行登录、注册等
//                .antMatchers("/**/common/**").permitAll()
//                .antMatchers("/**/user/page").hasAnyAuthority("admin","user")
                // 自定义匿名访问所有url放行 : 允许匿名和带权限以及登录用户访问
//                .antMatchers(anonymousUrls.toArray(new String[0])).permitAll()
                // 其他任何请求都需要身份认证
                .anyRequest().authenticated()
                .and()
//                .addFilter(new JwtAuthenticationFilter(authenticationManager()))
//                .addFilter(new JwtAuthorizationFilter(authenticationManager()))
                // 不需要session,不创建会话
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class)
                // 添加自定义未授权和未登录返回结果
                .exceptionHandling()
                .accessDeniedHandler(jwtAccessDeniedHandler)
                .authenticationEntryPoint(jwtAuthenticationEntryPoint)
                // 防止iframe 造成跨域
                .and()
                .headers()
                .frameOptions()
                .disable();
    }

    @Bean
    public IgnoreUrlsConfig ignoreUrlsConfig() {
        return new IgnoreUrlsConfig();
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
        return source;
    }
}

四、自定义JWT登录授权过滤器

(一)JwtAuthenticationTokenFilter 配置类

package com.cloud.wl.hp.config.component;

import com.cloud.wl.hp.base.utils.JwtTokenUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
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.Objects;

/**
 * jwt 登录授权过滤器
 *
 * @author wanglin
 * @version 1.0
 * @date 2022-03-02 周三
 */
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Value("${jwt.tokenHeader}")
    private String tokenHeader;

    @Value("${jwt.tokenHead}")
    private String tokenHead;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {


        String authHeader = request.getHeader(tokenHeader);
        //存在token
        String authToken = authHeader;
        if (StringUtils.isNotBlank(authHeader)) {
            //判断token是否是以 'Bearer ' 开头
            if (authHeader.startsWith(tokenHead)) {
                //截取'Bearer ' 后面的token
                authToken = authHeader.substring(tokenHead.length());
            }
            String username = jwtTokenUtil.getUserNameFromToken(authToken);
            //token 存在用户名,但未登录
            if (StringUtils.isNotBlank(username) && Objects.isNull(SecurityContextHolder.getContext().getAuthentication())) {
                //登录
                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                //验证token是否有效,重新设置用户对象
                if (jwtTokenUtil.validateToken(authToken, userDetails)) {
                    UsernamePasswordAuthenticationToken authenticationToken =
                            new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                    authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                }
            }
        }
        filterChain.doFilter(request, response);
    }
}

LoginUserDetails 类

五、自定义未授权和未登录返回结果

(一)自定义未授权返回类

JwtAccessDeniedHandler 类

package com.cloud.wl.hp.config.component;

import com.cloud.wl.hp.config.utils.SecurityUtils;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 没有访问权限
 *
 * @author wanglin
 * @version 1.0
 * @date 2022-02-28 周一
 */
@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException {
        SecurityUtils.resultFailMsg(response, HttpServletResponse.SC_FORBIDDEN, "权限不足,请联系管理员!", e.getMessage());
    }
}

(二)未登录返回类

JwtAuthenticationEntryPoint 类

package com.cloud.wl.hp.config.component;

import com.cloud.wl.hp.config.utils.SecurityUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 当为登录或者token无效时访问接口,自定义返回结果
 *
 * @author wanglin
 * @version 1.0
 * @date 2022-02-28 周一
 */
@Slf4j
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request,
                         HttpServletResponse response,
                         AuthenticationException authException) throws IOException {
        SecurityUtils.resultFailMsg(response, HttpServletResponse.SC_UNAUTHORIZED, "未登录,请登录!", authException.getMessage());

    }
}

(三)SecurityUtils 工具类

package com.cloud.wl.hp.config.utils;

import com.cloud.wl.hp.base.common.mapper.JsonMapperUtil;
import com.cloud.wl.hp.base.dto.RestResultDTO;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author wanglin
 * @version 1.0
 * @date 2022-01-19 周三
 */
public class SecurityUtils {

    /**
     * security 统一返回信息处理
     *
     * @param response
     * @param code
     * @param msg
     * @throws IOException
     */
    public static void resultSuccessMsg(HttpServletResponse response, Integer code, Object data, String msg) throws IOException {
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Cache-Control", "no-cache");

        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        response.setStatus(code);
        RestResultDTO result = new RestResultDTO<>(code, msg, data, null);
        response.getWriter().write(JsonMapperUtil.toJson(result));
        response.getWriter().flush();
    }

    /**
     * security 统一返回信息处理
     *
     * @param response
     * @param code
     * @param msg
     * @param exceptionMsg
     * @throws IOException
     */
    public static void resultFailMsg(HttpServletResponse response, Integer code, String msg, String exceptionMsg) throws IOException {
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Cache-Control", "no-cache");

        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        response.setStatus(code);
        RestResultDTO result = new RestResultDTO<>(code, msg, null, exceptionMsg);
        response.getWriter().write(JsonMapperUtil.toJson(result));
        response.getWriter().flush();
    }

    /**
     * security 统一返回信息处理
     *
     * @param response
     * @param code
     * @param msg
     * @throws IOException
     */
    public static void resultFailMsg(HttpServletResponse response, Integer code, String msg) throws IOException {
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Cache-Control", "no-cache");

        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        response.setStatus(code);
        RestResultDTO result = new RestResultDTO<>(code, msg, null, null);
        response.getWriter().write(JsonMapperUtil.toJson(result));
        response.getWriter().flush();
    }

    /**
     * security 统一返回信息处理
     *
     * @param response
     * @param msg
     * @throws IOException
     */
    public static void resultFailMsg(HttpServletResponse response, String msg) throws IOException {
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Cache-Control", "no-cache");

        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        RestResultDTO result = new RestResultDTO<>(0, msg, null, null);
        response.getWriter().write(JsonMapperUtil.toJson(result));
        response.getWriter().flush();
    }
}

六、自定义登录类 LoginUserDetails

(一)LoginUserDetails类——实现UserDetails类

LoginUserDetails 类

package com.cloud.wl.hp.config.model;

import com.cloud.wl.hp.domain.SysUser;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import java.util.Collection;
import java.util.Set;

/**
 * @author wanglin
 * @version 1.0
 * @date 2022-02-28 周一
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginUserDetails implements UserDetails {

    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    private String username;
    private String password;
    //实现类 SimpleGrantedAuthority
//    private Collection<? extends GrantedAuthority> authorities;
    private Set<SimpleGrantedAuthority> authorities;
    //只要有一个为false,就不可用用
    private Boolean isAccountNonExpired;
    private Boolean isAccountNonLocked;
    private Boolean isCredentialsNonExpired;
    private Boolean isEnabled;

    public LoginUserDetails(SysUser user, Set<SimpleGrantedAuthority> authorities) {
        this.username = user.getUserId();
        this.password = user.getPassword();
//        this.password = bCryptPasswordEncoder.encode(user.getPassword());
//        this.authorities = Collections.singleton(new SimpleGrantedAuthority(user.getRoleType()));
        this.authorities = authorities;
        this.isEnabled = user.getIsEnabled();
        this.isAccountNonExpired = user.getIsAccountNonExpired();
        this.isAccountNonLocked = user.getIsAccountNonLocked();
        this.isCredentialsNonExpired = user.getIsCredentialsNonExpired();
    }

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

    @Override
    public String getPassword() {
        return this.password;
    }

    @Override
    public String getUsername() {
        return this.username;
    }

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

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

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

    @Override
    public boolean isEnabled() {
        return true;
    }
}

(二)SysUser类

SysUser类

package com.cloud.wl.hp.domain;

import com.baomidou.mybatisplus.annotation.TableName;
import com.cloud.wl.hp.base.model.AbstractBaseDeleteSecurityModel;
import com.cloud.wl.hp.support.utils.Constants;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Table;

import javax.persistence.Column;
import javax.persistence.Entity;

/**
 * @author wanglin
 * @version 1.0
 * @date 2022-01-04 周二
 */
@Data
@EqualsAndHashCode(callSuper = true)
@Entity(name = SysUser.TABLE_NAME)
@Table(appliesTo = SysUser.TABLE_NAME, comment = "用户表")
@TableName(value = SysUser.TABLE_NAME)
public class SysUser extends AbstractBaseDeleteSecurityModel {
    public static final String TABLE_NAME = Constants.TABLE_PREFIX + "sys_user";

    @Column(columnDefinition = "varchar(32) comment '用户账号'", nullable = false)
    private String userId;
    @Column(columnDefinition = "varchar(50) comment '用户类型,参见RoleType枚举:admin,user'", nullable = false)
    private String roleType;
    @Column(columnDefinition = "varchar(20) comment '姓名'", nullable = false)
    private String name;
    @Column(columnDefinition = "varchar(20) comment '昵称'")
    private String nickname;
    @Column(columnDefinition = "varchar(20) comment '身份证号'")
    private String idNo;
    @Column(columnDefinition = "varchar(5) comment '性别'")
    private String sex;
    @Column(columnDefinition = "varchar(20) comment '手机号'")
    private String mobileNo;
    @Column(columnDefinition = "varchar(64) comment '密码'", nullable = false)
    private String password;
    @Column(columnDefinition = "varchar(30) comment '邮箱'")
    private String email;
    @Column(columnDefinition = "varchar(300) comment '登录凭证'")
    private String loginToken;
    @Column(columnDefinition = "int(1) not null default 0 comment '状态,0:正常 ;1:禁用'")
    private Integer status;
    @Column(columnDefinition = "bit not null default 1 comment '是否首次登录'")
    private Boolean isFirstLogin;
    @Column(columnDefinition = "varchar(200) comment '头像'")
    private String icon;
    @Column(columnDefinition = "bit not null default 0 comment '是否为VIP'")
    private Boolean isVip;

    @Column(columnDefinition = "varchar(200) comment '备注信息'")
    private String note;
}

关注林哥,持续更新哦!!!★,°:.☆( ̄▽ ̄)/$:.°★ 。

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值