【用户登录】模块之登录认证+鉴权业务逻辑

用户登录——⭐认证功能的流程图:

【用户登录】模块之登录认证+鉴权业务逻辑_第1张图片


⭐鉴权流程图:

【用户登录】模块之登录认证+鉴权业务逻辑_第2张图片


用户登录功能的Java代码实现

1. 实体类-User

orm框架:JPA

@Table(name = "user_tab")
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name="user_id")
    private Long id;

    @Column(name="user_name")
    private String username;

    @Column(name="user_password")
    private String password;

    @Column(name="user_phone")
    private String phone;

    @Column(name="user_nickname")
    private String nickname;

    @Column(name="user_create_by")
    private String createBy;
    @Column(name="user_create_time")
    private Date createTime;
    @Column(name="user_update_time")
    private Date updateTime;

    @Column(name="user_role_id")
    private Long roleId;
}

2. UserDao

@Repository
public interface UserDao extends JpaRepository {

    //根据user的username和password查询该用户
    User findByUsernameAndPassword(String username,String password);
}

3. UserService业务层接口

public interface UserService {

    //全查询
    List findAllUsers();

    //1027-【从数据库读取用户名信息存入布隆过滤器中】
    void warnUpUsernames();

    //1027-【用户登录】
    User login(String username, String password);

}

4. ⭐UserServiceImpl业务实现类

@Service
@Slf4j
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    //全查询
    @Override
    public List findAllUsers() {
        return userDao.findAll();
    }

    //1027-【从数据库读取用户名信息存入布隆过滤器中】
    @Override
    public void warnUpUsernames() {
        userDao.findAll().forEach(u -> {
                    stringRedisTemplate.opsForValue()
                            .getOperations()
                            .execute(new DefaultRedisScript(
                                            "return redis.call('bf.add',KEYS[1],ARGV[1])"
                                            , Long.class),
                                    new ArrayList() {{
                                        add("whiteUsernames");
                                    }}, u.getUsername()
                            );
                    }
        );
    }

    //1027-【用户登录】
    @Override
    public User login(String username, String password) {
        if(!StringUtils.hasText(username)){
            throw new UsernameIsEmptyException("用户名为空异常");
        }
        username = username.trim();
        if(checkFromWhite(username)){
            throw new UsernameNotFoundException("用户名不存在异常");
        }

        User user = userDao.findByUsernameAndPassword(username, password);

        if(Objects.isNull(user)){
            throw  new BadCredentialsException("用户名|密码错误");
        }

        return user;
    }

    //判断用户名是否存在于布隆过滤器
    private boolean checkFromWhite(String username) {
        Long isExist = stringRedisTemplate.opsForValue()
                .getOperations()
                .execute(new DefaultRedisScript("return redis.call('bf.exists',KEYS[1],ARGV[1])", Long.class),
                        new ArrayList() {{
                            add("whiteUsernames");
                        }}, username);

        return isExist.intValue() == 0;
    }

5. ⭐UserController控制层接口

@Api(tags = "用户模块接口")
@RestController
@RequestMapping("/api/user")
@Slf4j
public class UserController {

    @Autowired
    private UserService userService;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;


    //1027-【全查询】
    @ApiOperation(value = "findAllUsers",notes = "查询所有用户,需要当前用户登录状态")
    @GetMapping("/findAllUsers")
    @BmsRole(value="1")
    public HttpResp> findAllUsers(){
        return HttpResp.success(userService.findAllUsers());
    }


    //1027-【用户登录】
    @ApiOperation(value = "login",notes = "用户登录")
    @GetMapping("login")
    public HttpResp login(HttpServletResponse response, String username, String password){
        //首先调用AuthorityService的login方法进行用户登录验证,返回一个User对象。
        User user = userService.login(username,password);

        //然后生成一个JWT作为用户的身份认证凭证,其中包含了用户名和过期时间等信息。
        //使用JWT.create()创建JWT对象,并使用withClaim()方法设置用户名,withExpiresAt()方法设置过期时间。
        String salt = Base64.getEncoder().encodeToString((username+":"+password).getBytes(StandardCharsets.UTF_8));


        log.debug("user:{}",user);
        String token = JWT.create()
                .withClaim("username", username)
                .withClaim("roleId",""+user.getRoleId())
                .withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 30))   //30分钟令牌过期
                .sign(Algorithm.HMAC256(salt)); //使用Algorithm.HMAC256(salt)指定加密算法和密钥,对JWT进行签名。
        log.debug("用户验证通过,生成token为:{}",token);

        //将生成的JWT存储到Redis缓存中,使用stringRedisTemplate.opsForValue().set()方法设置键值对,并使用stringRedisTemplate.expire()方法设置过期时间
        stringRedisTemplate.opsForValue().set(token,salt);
        stringRedisTemplate.expire(token,60, TimeUnit.MINUTES); //60分钟redis缓存token过期
        response.addCookie(new Cookie("token",token));  //将JWT作为Cookie添加到HTTP响应中,使用response.addCookie()方法

        return HttpResp.success(user);  //最后返回一个成功的响应,消息体中包含了登录成功的用户对象
    }

}

用户认证解决方案

1. 配置部署拦截器

@Configuration
@Slf4j
public class BmsMvcConfig implements WebMvcConfigurer {

    @Bean
    public AuthorityInterceptor authorityInterceptor(){
        log.debug("BmsMvc拦截器启动成功:{}..........",new Date());
        return new AuthorityInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authorityInterceptor())
                .addPathPatterns("/api/**")
                .excludePathPatterns(
                        "/api/user/login"
                );
    }
}

2. ⭐自定义拦截器

@Slf4j
public class AuthorityInterceptor implements HandlerInterceptor {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     *
     * @param request
     * @param response
     * @param handler   当前拦截器拦截的方法
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.debug("已进入拦截器:{}",new Date());

        String token = request.getHeader("token");

        //1.从redis中读取token,如果不存在,则用户是非法用户,抛出自定义异常类InvalidTokenException
        Boolean isRedis = stringRedisTemplate.hasKey(token);

        if(!isRedis) throw new InvalidTokenException("无效的token");

        String salt = stringRedisTemplate.opsForValue().get(token);
        log.debug("salt:{}",salt);
        DecodedJWT decodedJWT = JWT.require(Algorithm.HMAC256(salt)).build().verify(token);

        //2.token验证完成正确
        String roleId = decodedJWT.getClaim("roleId").asString();
        log.debug("------->roleId:{}",roleId);

        HandlerMethod method = (HandlerMethod) handler;
        //System.out.println("----->"+method.getMethod().getDeclaredAnnotation(GetMapping.class));
        BmsRole bmsRole= method.getMethod().getDeclaredAnnotation(BmsRole.class);
        String requiredRolId = bmsRole.value();

        if(!roleId.equals(requiredRolId)){
            throw new PermissionDeniedException("您没有足够的权限");
        }

        //获取请求对象的角色名称
        //获取请求的地址(uri)
//        String requestURI = request.getRequestURI();
//        requestURI = requestURI.substring(requestURI.lastIndexOf("/") + 1);
//        log.debug("请求路径uri:{}",requestURI);//URL/URI
//        System.out.println(handler.getClass());
        
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }
}

3. 在spring.factories文件加载部署

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.****.bms.authority.config.BmsMvcConfig,com.****.bms.authority.handler.UserExceptionHandler

未完待续......

你可能感兴趣的:(java,redis,拦截器,java,JWT,redis)