最近我的一个朋友独立开发了一个小程序,他给我看了下后台数据,短短几天用户已经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