服务器|登录会话模型实战


登录会话模型实战

  • 简介
  • 操作流程
    • 数据库设计
    • curd操作工具
    • 登录鉴权实现
      • 登录代码
      • 用户资源查询
      • 登录接口
    • 认证和鉴权
      • ApplicationUtil工具类
      • GrantedAuthority权限控制类
      • security Filter鉴权
  • 测试
  • 总结
  • 源码

简介 登录会话是基本操作,不管哪个应用都会用到的模块,针对于此,个人简笔编写了一个简单案例,针对前面spring security的功能文章进行完善
操作流程 数据库设计 操作链接:用户中心-数据库设计
curd操作工具 操作链接:springboot植入pagerHelper和spring mybatis更新几种操作
登录鉴权实现 登录代码
这里制作简单的用户名和密码操作,查询用户资源路径,通过用户电话号码查询用户信息,匹配密码,具体代码如下:
/** * 电话号码登录 * @param phone 用户电话号码 * @param password 加密后密码 * @return */ @Override public UserData login(String phone, @NotNull String password) {Example example = new Example(User.class); example.createCriteria().andEqualTo("userPhone", phone); User user = userMapper.selectOneByExample(example); if (user != null && password.equals(user.getUserPassword())) {UserData userData = https://www.it610.com/article/new UserData(); /** * 查询用户资源信息 */ userData.setUserResources(resourceMapper.selectResourceByRoleId(user.getId())); BeanUtils.copyProperties(user, userData); Logon logon = new Logon(); logon.setToken(UUID.randomUUID().toString().replaceAll("-", "")); logon.setUserId(user.getId()); try {logon.setResourceData(objectMapper.writeValueAsString(userData.getUserResources())); } catch (JsonProcessingException e) {e.printStackTrace(); } Example logonExample = new Example(Logon.class); logonExample.createCriteria().andEqualTo("userId", user.getId()); logonMapper.deleteByExample(logonExample); logonMapper.insert(logon); userData.setToken(logon.getToken()); return userData; } return null; }

用户资源查询
查询用户资源的sql配置
id="selectResourceByRoleId" resultMap="BaseResultMap"> SELECT `tb_resource`.`id`, `resource_code`, `resource_url`, `resource_description`, `resource_state` FROM`tb_resource` RIGHTJOIN`role_resource` ON `tb_resource`.`id` = `role_resource`.`resource_id` RIGHTJOIN `user_role` ON `user_role`.`role_id` = `role_resource`.`role_id` WHERE `user_role`.`user_id` = #{userId}

登录接口
具体登录的controller代码
package com.lgh.controller; import com.lgh.common.result.CommonResult; import com.lgh.common.result.inter.IResult; import com.lgh.model.domain.UserData; import com.lgh.service.ILogonService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import javax.validation.Valid; import javax.validation.constraints.NotNull; @Api(tags = "登录服务") @RestController @RequestMapping("/login") @Validated public class LogonController {@Autowired private ILogonService logonService; @ApiOperation("登录服务接口") @PostMapping("/sign") @Valid public IResult login(@NotNull @RequestParam("phone") String phone, @NotNull @RequestParam("password") String password) {UserData userData = https://www.it610.com/article/logonService.login(phone, password); return CommonResult.successData(userData); } }

认证和鉴权 通过spring security的过滤器进行拦截认证,通过注解@RolesAllowed授权。由于spring security的filter不能给spring管理,否则会全局拦截,因此这里要获取请求接口,的拿到service的bean,所以先获取application的类。
ApplicationUtil工具类
package com.lgh.common.util; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; @Component public class ApplicationUtil implements ApplicationContextAware {private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext; }public static T getBean(Class t) {return applicationContext.getBean(t); } }

GrantedAuthority权限控制类
由于jsr250会添加前缀,个人不太喜欢前缀的ROLE_,因此我实现GrantedAuthority时也默认给前缀,如下代码
package com.lgh.common.authority.authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.util.Assert; public class MySimpleGrantedAuthority implements GrantedAuthority {private static final long serialVersionUID = 510L; private final String rolePrefix = "ROLE_"; private final String role; public MySimpleGrantedAuthority(String role) {Assert.hasText(role, "A granted authority textual representation is required"); this.role = role; }public String getAuthority() {return rolePrefix + this.role; }public boolean equals(Object obj) {if (this == obj) {return true; } else {return obj instanceof MySimpleGrantedAuthority ? this.role.equals(((MySimpleGrantedAuthority) obj).role) : false; } }public int hashCode() {return this.role.hashCode(); }public String toString() {return this.role; } }

security Filter鉴权
下面我们来编写实际的过滤器
package com.lgh.common.authority.filter; import com.fasterxml.jackson.databind.ObjectMapper; import com.lgh.common.authority.authentication.MyAuthentication; import com.lgh.common.authority.authentication.MySimpleGrantedAuthority; import com.lgh.common.authority.entity.UserDetail; import com.lgh.common.result.CommonResult; import com.lgh.common.util.ApplicationUtil; import com.lgh.model.Resource; import com.lgh.model.domain.UserData; import com.lgh.service.ILogonService; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.util.StringUtils; 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.ArrayList; import java.util.List; import java.util.stream.Collectors; /** * spring security过滤器,不要交给spring 管理 */ public class MyAuthenticationFilter extends OncePerRequestFilter {@Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {String token = request.getHeader("token"); if (StringUtils.isEmpty(token)) {filterChain.doFilter(request, response); } else {ILogonService logonService = ApplicationUtil.getBean(ILogonService.class); UserData userData = https://www.it610.com/article/logonService.verify(token); if (userData == null) {ObjectMapper objectMapper = ApplicationUtil.getBean(ObjectMapper.class); response.setContentType("application/json; charset=utf-8"); response.getWriter().print(objectMapper.writeValueAsString(CommonResult.deny())); return; } else {UserDetail userDetail = new UserDetail(); userDetail.setId(userData.getId()); userDetail.setName(userData.getUserName()); List roles = new ArrayList<>(); if (userData.getUserResources() != null) {roles = userData.getUserResources().stream() .map(Resource::getResourceCode) .map(MySimpleGrantedAuthority::new) .collect(Collectors.toList()); } MyAuthentication myAuthentication = new MyAuthentication(userDetail, roles); SecurityContextHolder.getContext().setAuthentication(myAuthentication); filterChain.doFilter(request, response); } } } }

测试
  1. 初始化用户信息,自己根据表格初始化
  2. swagger测试,如下图
    服务器|登录会话模型实战
    文章图片
  3. 登录完后,拿到对应的token,请求样例,如图
    服务器|登录会话模型实战
    文章图片
总结
  1. 从文章中可以学到简单的用户中心用户设计
  2. 如何使用pagerHelper功能
  3. 登录流程设计
  4. security鉴权使用和设计以及常见问题(过滤器不能交给spring,设置路径不校验web.ignoring(),权限校验前缀的处理
源码 【服务器|登录会话模型实战】github

    推荐阅读