SpringBoot + SpringSecurity + JWT

CustomerUserDetailsService的loadUserByUsername用来自定义用户信息和定义密码的编码
CustomerOncePerRequestFilter处理器是每次请求都会进去的;
   当是WebSecurityConfig的permitAll范围,不管有没有setAuthentication(authentication)都有权限
   当是WebSecurityConfig的authenticated,需要setAuthentication才有权限

一、pom依赖

1.POM依赖
	  <!--security-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
            <version>2.2.4.RELEASE</version>
        </dependency>
        <!--jwt-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

二、UserDetails

1、CustomerUserDetails实现security的UserDetails 接口

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class CustomerUserDetails implements UserDetails {

    private String userId;
    private String userName;
    private String account;
    private String password;
    private Boolean available;
    private Boolean adminFlag;
    private Boolean validUrlFlag;
    private Collection<? extends GrantedAuthority> authorities;


    public CustomerUserDetails(String userId, String userName, String account, Collection<? extends GrantedAuthority> authorities) {
        this.setUserId(userId);
        this.setAccount(account);
        this.setUserName(userName);
        this.setAuthorities(authorities);
    }

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

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

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

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

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

2、CustomerUserDetailsService实现security的UserDetailsService接口

@Component
public class CustomerUserDetailsService implements UserDetailsService {

    private final UserService userService;

    public CustomerUserDetailsService(UserService userService) {
        this.userService = userService;
    }

    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
        UserListVO userListVO = userService.getByUserName(userName);
        List<GrantedAuthority> authorityList = new ArrayList<>();
        authorityList.add(new SimpleGrantedAuthority("ROLE_USER"));
        authorityList.add(new SimpleGrantedAuthority("角色2"));
        return new CustomerUserDetails(userListVO.getId(), userListVO.getName(), userListVO.getName(), authorityList);
    }
}

三、自定义处理器

1、CustomerOncePerRequestFilter继承OncePerRequestFilter类

这个处理器一定要有,核心代码SecurityContextHolder.getContext().setAuthentication(authentication)才有权限,不然会提示没有权限,也才能获取当前用户

@Component
public class CustomerOncePerRequestFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("进入自定义jwt过滤器");

        //1.解析jwt获取用户信息
//        UserDetailVO userDetailVO = dbUser();
        //2.用户信息转成CustomerUserDetails对象
        CustomerUserDetails customerUserDetails = new CustomerUserDetails("用户id1", "用户名称1", "登录账号1", new ArrayList<>());
        //3.customerUserDetails 转成UsernamePasswordAuthenticationToken对象
        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(customerUserDetails, "123451", customerUserDetails.getAuthorities());
        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
        //4将UsernamePasswordAuthenticationToken信息放入到上下文对象中, 这样后面的过滤器看到我们上下文对象中有authentication对象,就相当于我们认证过了
        SecurityContextHolder.getContext().setAuthentication(authentication);
        //5.放行
        chain.doFilter(request, response);
    }
}

2、认证失败处理器

/**
 * 认证失败处理器
 */
@Component
public class SecurityAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) {
        System.out.println("进入token认证失败处理器");
        throw new RuntimeException("token无效,请重新登录");
    }
}

3、权限不足处理器

/**
 * 权限不足处理器
 */
@Component
public class SecurityAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException {
        System.out.println("进入权限不足处理器");
    }
}

四、配置类

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {


    @Resource
    private SecurityAccessDeniedHandler restfulAccessDeniedHandler;         //权限不足处理器

    @Resource
    private SecurityAuthenticationEntryPoint restAuthenticationEntryPoint;  //认证失败处理器

    @Resource
    private CustomerOncePerRequestFilter customerOncePerRequestFilter;                            //自己的jwt过滤器

    /**
     * 自定义加密器Bean
     * 强散列哈希加密实现,在Controller新增用户的时候用此bean生成密码,与及修改密码的时候用到此bean比较新旧密码是否匹配
     */
//    @Bean
//    public PasswordEncoder passwordEncoder() {
//        return new BCryptPasswordEncoder();
//    }


    /**
     * 定义AuthenticationManager
     * 声明它的作用是用它帮我们进行认证操作,调用这个Bean的authenticate方法会由Spring Security自动帮我们做认证
     */
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//        auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
        auth.userDetailsService(userDetailsService());
    }


    /**
     * anyRequest          |   匹配所有请求路径
     * access              |   SpringEl表达式结果为true时可以访问
     * anonymous           |   匿名可以访问
     * denyAll             |   用户不能访问
     * fullyAuthenticated  |   用户完全认证可以访问(非remember-me下自动登录)
     * hasAnyAuthority     |   如果有参数,参数表示权限,则其中任何一个权限可以访问
     * hasAnyRole          |   如果有参数,参数表示角色,则其中任何一个角色可以访问
     * hasAuthority        |   如果有参数,参数表示权限,则其权限可以访问
     * hasIpAddress        |   如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
     * hasRole             |   如果有参数,参数表示角色,则其角色可以访问
     * permitAll           |   用户可以任意访问
     * rememberMe          |   允许通过remember-me登录的用户访问
     * authenticated       |   用户登录后可访问
     */
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .csrf().disable()                                                             //由于使用的是JWT,我们这里不需要csrf
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)   //基于token,所以不需要session
                .and()
                .authorizeRequests()
                .antMatchers(HttpMethod.GET, //允许对于网站静态资源的无授权访问
                        "/",
                        "/*.html",
                        "/favicon.ico",
                        "/**/*.html",
                        "/**/*.css",
                        "/**/*.js",
                        "/swagger.**",
                        "/v3.**",
                        "**_notify_url"
                ).permitAll()
                .antMatchers("/user/login", "/Register").permitAll()         //对登录注册要允许匿名访问
                .antMatchers(HttpMethod.OPTIONS).permitAll()                    //跨域请求会先进行一次options请求
//                .antMatchers("/**").permitAll()                               //测试时全部运行访问
                .anyRequest()                                                   //除上面外的所有请求全部需要鉴权认证
                .authenticated()

                .and()
                .headers().cacheControl()      //禁用缓存
        ;


        //添加认证失败处理器和权限不足处理器
        httpSecurity.exceptionHandling()
                .authenticationEntryPoint(restAuthenticationEntryPoint)   //认证失败处理器
                .accessDeniedHandler(restfulAccessDeniedHandler);         //权限不足处理器


        //配置自己的JWT过滤器
        httpSecurity.addFilterBefore(customerOncePerRequestFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

五、获取当前用户SecurityUtils

 public static Object getPrincipal() {
     return SecurityContextHolder.getContext().getAuthentication().getPrincipal();
 }
public class SecurityUtils {
    private static final String USER_ID_KEY = "userId";
    private static final String USERNAME_KEY = "username";
    private static final String ADMIN_FLAG_KEY = "adminFlag";
    private static final String VALID_URL_FLAG = "validUrlFlag";

    /**
     * 判断是否登录
     *
     * @return 是否登录
     */
    public static boolean isLogin() {
        Object principal = getPrincipal();
        boolean isLogin = true;
        if (principal instanceof String) {
//            isLogin = !Constants.ANONYMOUS_USER.equals(principal.toString());
        }
        return isLogin;
    }

    /**
     * 返修操作人ID
     *
     * @return 操作人ID
     */
    public static String getOperatorId() {
        Object principal = getPrincipal();
        if (principal instanceof LinkedHashMap) {
            return ((LinkedHashMap) principal).get(USER_ID_KEY).toString();
        } else if (principal instanceof CustomerUserDetails) {
            return ((CustomerUserDetails) principal).getUserId();
        } else {
            return "";
        }
    }

    /**
     * 返回登录人员是否admin
     *
     * @return 是否admin
     */
    public static Boolean isAdmin() {
        Object principal = getPrincipal();
        if (principal instanceof LinkedHashMap) {
            return (boolean) ((LinkedHashMap) principal).get(ADMIN_FLAG_KEY);
        } else if (principal instanceof CustomerUserDetails) {
            return ((CustomerUserDetails) principal).getAdminFlag();
        } else {
            return null;
        }
    }

    /**
     * 返回登录人员是否admin
     *
     * @param principal 授权信息map
     * @return 是否admin
     */
    public static Boolean isAdmin(Object principal) {
        if (principal instanceof LinkedHashMap) {
            return (boolean) ((LinkedHashMap) principal).get(ADMIN_FLAG_KEY);
        } else if (principal instanceof CustomerUserDetails) {
            return ((CustomerUserDetails) principal).getAdminFlag();
        } else {
            return null;
        }
    }

    /**
     * 返回是否验证url
     *
     * @param principal 授权信息map
     * @return 是否验证url
     */
    public static Boolean isValidUrl(Object principal) {
        if (principal instanceof LinkedHashMap) {
            return (boolean) ((LinkedHashMap) principal).get(VALID_URL_FLAG);
        } else if (principal instanceof CustomerUserDetails) {
            return ((CustomerUserDetails) principal).getValidUrlFlag();
        } else {
            return null;
        }
    }

    /**
     * 返回登录人员名称
     *
     * @return 登录人员名称
     */
    public static String getOperatorName() {
        Object principal = getPrincipal();
        if (principal instanceof LinkedHashMap) {
            return ((LinkedHashMap) principal).get(USERNAME_KEY).toString();
        } else if (principal instanceof CustomerUserDetails) {
            return ((CustomerUserDetails) principal).getUsername();
        } else {
            return "";
        }
    }

    /**
     * 获取用户详情
     *
     * @return 用户详情
     */
    public static Object getPrincipal() {
        return SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    }
}

六、登录业务,查询数据库生成token

    @Override
    public UserLoginVO login(UserLoginDTO userLoginDTO, HttpServletRequest request, HttpServletResponse response) {

        String userName = userLoginDTO.getUserName();
        String userPwd = userLoginDTO.getUserPassword();

        //1.根据用户名查找用户
        UserListVO dbUser = getByUserName(userName);
        if (dbUser == null) {
            throw new RuntimeException("登录用户不存在");
        }

        //2.判断密码是否一致
        //SHA1加密
//        userPwd = SHA1Util.encrytSHA1(userName, userPwd);
        //密码不正确
        if (!Objects.equals(userPwd, dbUser.getPassword())) {
            throw new RuntimeException("用户密码错误");

        }

        //3.构建jwt负载信息对象,并生成jwt
        UserJWTPayLoadBO userJWTPayLoadBO = new UserJWTPayLoadBO();
        userJWTPayLoadBO.setUserId(dbUser.getId());
        userJWTPayLoadBO.setUserName(dbUser.getName());
        String ip = JwtUtils.getClientHost(request);
        userJWTPayLoadBO.setIpAddress(ip);

        Map<String, Object> userJWTPayLoadMap = JSONObject.parseObject(JSONObject.toJSONString(userJWTPayLoadBO), new TypeReference<>() {
        });
        String jwt = JwtUtils.generateJWT(userJWTPayLoadMap);

        //4.jwt信息存入token
        Cookie cookie = new Cookie(CommonConstants.USER_TOKEN, jwt);
        cookie.setPath("/");
        cookie.setMaxAge(60 * 60 * 24 * 7);
        response.addCookie(cookie);

        //5.返回用户信息和jwt
        UserLoginVO userLoginVO = new UserLoginVO();
        userLoginVO.setToken(jwt);
        userLoginVO.setUserInfo(dbUser);
        return userLoginVO;
    }
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

飘然生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值