教你做一个自己的App

2022-09-07 11:12:57 浏览数 (2)

最近我的一个朋友独立开发了一个小程序,他给我看了下后台数据,短短几天用户已经20w了,这个小程序不是专对女性,但女性用户却占了2/3,说实话确实有点羡慕。

尤其是那货一副老子掌握了约x密码的欠揍表情,所以最近也在琢磨着要不也开发一个自己的应用。

这个事说难不难,但说简单也没那么简单,就是担心遇到盲区遇到坑,这种东西一多,自己玩着玩着也就没啥信心了。

所以我的计划是先把前端部分需要的东西,常用组件什么的,整体先过一遍,也是捋清思路,对于后端其实还是比较放心,因为我本身就是一直在开发这方面。

前端部分到目前为止还算顺利,整体感觉就是现在的封装好的组件特别多,只要不是非要搞系统上的什么功能应该不太会是什么大麻烦。

到这,本来以为接下来应该会很顺畅了,但是万万没想到最后的最后竟然是后端的东西把我卡了一下...

哪地方呢?开发过登录的同学可能都知道Springsecurity和JWT,这东西我几年前做项目的时候倒是用过,时间一久还是有点生疏了。

于是这两天稍微整理了下,想达到的目的就是能实现注册,能实现登录,能验证登录也能方便调试接口就行,目前就先这样。

所以我继续去找了下网上的资料,毕竟做过还是看几遍就懂了,但是如果让我独立搭一份,估计光pom文件里各种jar包的版本搭配就要调个半天。

算了,还是索性在github上找了一个开源的项目,精简精简就是个脚手架,于是下载了几个高star项目,对比了下发现mail-tiny还不错。

看代码的时候却傻眼了,mail-tiny里对于security的使用和之前找到的资料里的内容似乎不太一样。

不过好在大概逻辑还是一样的,而且代码也更成熟一点。

接下来,我就大概讲下这部分内容

核心

这个项目里springSecurity主要核心还在在它的主配置里

可以通过以下配置来注册自定义JWT权限拦截器,通过定义好的JWT解析器,来验证从header传入的token是否有效

代码语言:javascript复制
registry.and().addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

验证token

验证token主要使用以下方法,

代码语言:javascript复制
@Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws ServletException, IOException {
        String authHeader = request.getHeader(this.tokenHeader);
        if (authHeader != null && authHeader.startsWith(this.tokenHead)) {
            String authToken = authHeader.substring(this.tokenHead.length());// The part after "Bearer "
            String username = jwtTokenUtil.getUserNameFromToken(authToken);
            LOGGER.info("checking username:{}", username);
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
                if (jwtTokenUtil.validateToken(authToken, userDetails)) {
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    LOGGER.info("authenticated user:{}", username);
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
        }
        chain.doFilter(request, response);
    }

如果token过期或者因为其他原因失效,则执行以下方法

代码语言:javascript复制
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Cache-Control","no-cache");
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        response.getWriter().println(JSONUtil.parse(CommonResult.unauthorized(authException.getMessage())));
        response.getWriter().flush();
    }
}

如果没有权限访问,则会执行以下操作

代码语言:javascript复制
public class RestfulAccessDeniedHandler implements AccessDeniedHandler{
    @Override
    public void handle(HttpServletRequest request,
                       HttpServletResponse response,
                       AccessDeniedException e) throws IOException, ServletException {
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Cache-Control","no-cache");
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        response.getWriter().println(JSONUtil.parse(CommonResult.forbidden(e.getMessage())));
        response.getWriter().flush();
    }
}

跨域

同时也可以注册,允许跨域请求

代码语言:javascript复制
//允许跨域请求的OPTIONS请求
registry.antMatchers(HttpMethod.OPTIONS)
    .permitAll();

可以设置不用token就可以访问的方法

代码语言:javascript复制
for (String url : ignoreUrlsConfig.getUrls()) {
   registry.antMatchers(url).permitAll();
}

JWT

注册的话也是通过JWT解析器,将密码加密后加到数据库里

代码语言:javascript复制
public UmsAdmin register(UmsAdminParam umsAdminParam) {
        UmsAdmin umsAdmin = new UmsAdmin();
        BeanUtils.copyProperties(umsAdminParam, umsAdmin);
        umsAdmin.setCreateTime(new Date());
        umsAdmin.setStatus(1);
        //查询是否有相同用户名的用户
        QueryWrapper<UmsAdmin> wrapper = new QueryWrapper<>();
        wrapper.lambda().eq(UmsAdmin::getUsername,umsAdmin.getUsername());
        List<UmsAdmin> umsAdminList = list(wrapper);
        if (umsAdminList.size() > 0) {
            return null;
        }
        //将密码进行加密操作
        String encodePassword = passwordEncoder.encode(umsAdmin.getPassword());
        umsAdmin.setPassword(encodePassword);
        baseMapper.insert(umsAdmin);
        return umsAdmin;
    }

登录的话就是验证传入的账号密码是否和数据库里存储的一致,如果一致则返回token。

代码语言:javascript复制
public String login(String username, String password) {
        String token = null;
        //密码需要客户端加密后传递
        try {
            UserDetails userDetails = loadUserByUsername(username);
            if(!passwordEncoder.matches(password,userDetails.getPassword())){
                Asserts.fail("密码不正确");
            }
            if(!userDetails.isEnabled()){
                Asserts.fail("帐号已被禁用");
            }
            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authentication);
            token = jwtTokenUtil.generateToken(userDetails);
            insertLoginLog(username);
        } catch (AuthenticationException e) {
            LOGGER.warn("登录异常:{}", e.getMessage());
        }
        return token;
    }

目前为止,如果做一般的登录验证应该也就足够了。

mail-tiny的地址如下,有兴趣的可以下出来看看 https://github.com/macrozheng/mall-tiny

0 人点赞