SpringCloud游戏平台改造-Day2

Day2

今天主要目的是接入SpringSecurity和JWT,不多说开干!

Day1

Day2

接入SpringSecurity

Step1

实现来自SpringSecurity的UserDetailService接口,实现它的loaduserByUserName, 实现与数据库中数据的绑定,后续也是通过这个进行验证

Step2

实现ScurityConfig类,配置密码的加密解密,公开链接/拦截链接的配置。以及一些Filter的加载

    /**
     * 密码加密
     * @return
     */

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                // 下面的链接是公开的
                .antMatchers("/user/account/token/""/user/account/register/""/websocket/**""/test").permitAll()
                .antMatchers("/pk/start/game/").hasIpAddress("127.0.0.1")
                .antMatchers("/pk/receive/bot/move/").hasIpAddress("127.0.0.1")
                .antMatchers(HttpMethod.OPTIONS).permitAll()
                .anyRequest().authenticated();
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }

Step3

实现JwtUtil,解析和生成JwtToken

    public static String getUUID() {
        return UUID.randomUUID().toString().replaceAll("-""");
    }

    /**
     * 生成JWT-token
     * @param subject
     * @return
     */

    public static String createJWT(String subject) {
        JwtBuilder builder = getJwtBuilder(subject, null, getUUID());
        return builder.compact();
    }

    /**
     * 获取JWT builder
     * @param subject
     * @param ttlMills
     * @param uuid
     * @return
     */

    private static JwtBuilder getJwtBuilder(String subject, Long ttlMills, String uuid) {
        // 1. 指定签名算法
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        // 2. 生成秘钥
        SecretKey secretKey = generalKey();
        long nowMills = System.currentTimeMillis();
        Date now = new Date(nowMills);
        if (ttlMills == null) {
            ttlMills = Constants.JwtConstants.JWT_TTL;
        }
        long expMills = ttlMills + nowMills;
        Date expDate = new Date(expMills);
        return Jwts.builder().setId(uuid).setSubject(subject)
                .setIssuer("chin").setIssuedAt(now).signWith(signatureAlgorithm, secretKey)
                .setExpiration(expDate);
    }

    /**
     * 生成秘钥
     * @return
     */

    public static SecretKey generalKey() {
        byte[] encodeKey = Base64.getDecoder().decode(Constants.JwtConstants.JWT_KEY);
        return new SecretKeySpec(encodeKey, 0, encodeKey.length, "HmacSHA256");
    }

    public static Claims parseJWT(String jwt) throws Exception {
        SecretKey secretKey = generalKey();
        return Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(jwt)
                .getBody();
    }

Step4

在网关处实现Filter,去拦截header处的JwtToken, 拦截出来的JWTToken将他进行解析,绑定在我们的SecurityContextHolder里面,在之后的getInfo的时候可以将他取出。

Step5

其余配置,如跨域配置

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) res;
        HttpServletRequest request = (HttpServletRequest) req;

        String origin = request.getHeader("Origin");
        if(origin!=null) {
            response.setHeader("Access-Control-Allow-Origin", origin);
        }

        String headers = request.getHeader("Access-Control-Request-Headers");
        if(headers!=null) {
            response.setHeader("Access-Control-Allow-Headers", headers);
            response.setHeader("Access-Control-Expose-Headers", headers);
        }

        response.setHeader("Access-Control-Allow-Methods""*");
        response.setHeader("Access-Control-Max-Age""3600");
        response.setHeader("Access-Control-Allow-Credentials""true");

        chain.doFilter(request, response);
    }

实现接口和方法

领域层

领域层我目前的设计是,分为了安全配置领域和用户领域两个部分。

  • 安全配置领域就是完成SpringSecurity相关的一系列配置
  • 用户领域就是完成获取jwt-token、用户登录注册等增删改查的操作

贴出主要代码:

/**
 * @author qi
 */

@Service
public class UserService implements IUserService {

    private Logger logger = LoggerFactory.getLogger(UserService.class);

    @Resource
    private IUserRepository userRepository;

    @Resource
    private AuthenticationManager authenticationManager;

    @Override
    public Map<String, String> token(String username, String password) {
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
        // 1. 让Security去完成登录
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);
        UserDetailsImpl user = (UserDetailsImpl) authenticate.getPrincipal();
        UserDetail userDetail = user.getUserDetail();

        // 2. 生成jwt-token
        String jwt = JwtUtil.createJWT(userDetail.getId().toString());
        logger.info("user: {} , token: {}", userDetail, jwt);
        Map<String, String> resultMap = new HashMap<>();
        resultMap.put("error_message""success");
        resultMap.put("token", jwt);
        return resultMap;
    }

    @Override
    public Map<String, String> userInfo() {
        UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
        UserDetailsImpl user = (UserDetailsImpl) authentication.getPrincipal();
        UserDetail userDetail = user.getUserDetail();

        Map<String, String> resultMap = new HashMap<>();
        resultMap.put("error_message""success");
        resultMap.put("id", userDetail.getId().toString());
        resultMap.put("username", userDetail.getUsername());
        resultMap.put("photo", userDetail.getPhoto());
        resultMap.put("rating", userDetail.getRating().toString());
        return resultMap;
    }

    @Override
    public Map<String, String> insertUser(String username, String password, String photo, Integer rating) {
        userRepository.insertUser(username,
                password,
                photo,
                rating);
        Map<String, String> resultMap = new HashMap<>();
        resultMap.put("error_message""success");
        return resultMap;
    }

    @Override
    public Map<String, String> updateUser(String username, String photo, Integer rating) {
        boolean success = userRepository.updateUser(username, photo, rating);
        Map<String, String> resultMap = new HashMap<>();
        if (success) {
            resultMap.put("error_message""success");
        } else {
            resultMap.put("error_message""delete failed");
        }
        return resultMap;
    }

    @Override
    public Map<String, String> checkUserToken(String token) {
        return null;
    }

    @Override
    public Map<String, String> deleteUser(Integer uId) {
        boolean success = userRepository.deleteUser(uId);
        Map<String, String> resultMap = new HashMap<>();
        if (success) {
            resultMap.put("error_message""success");
        } else {
            resultMap.put("error_message""delete failed");
        }
        return resultMap;
    }


}

仓储层

仓储层目前设计还是比较简单,就是基于User这个表的pojo,dao以及实现了领域层的接口,并设计了一些常量。

接口层

接口层就是实现了提供的两个接口,具体如下:

    @PostMapping("/user/account/token/")
    public Map<String, String> getToken(@RequestParam Map<String, String> map) {
        logger.info("username: {}", map.get("username"));
        return userService.token(map.get("username"), map.get("password"));
    }

    @GetMapping("/user/account/info/")
    public Map<String, String> getInfo() {
        return userService.userInfo();
    }

应用程序层

这里由于逻辑都是较为简单,所以就是规范了一下接口层所要用到的结果输入输出。

/**
 * @author qi
 */

public interface IUserService {

    /**
     * 登录并分发JWT-token
     * @return
     */

    Map<String, String> token(String username, String password);

    /**
     * 获取用户信息
     * @return
     */

    Map<String, String> userInfo();

    /**
     * 注册用户
     * @param username
     * @param password
     * @param photo
     * @param rating
     * @return
     */

    Map<String, String> insertUser(String username, String password, String photo, Integer rating);

    /**
     * 更新用户
     * @param username
     * @param photo
     * @param rating
     * @return
     */

    Map<String, String> updateUser(String username, String photo, Integer rating);

    /**
     * 检查用户的JWT=token是否合法
     * @param token
     * @return
     */

    Map<String, String> checkUserToken(String token);

    /**
     * 删除用户
     * @param uId
     * @return
     */

    Map<String, String> deleteUser(Integer uId);
}

总结

今天是为了实现登录的内容,后续增删改查的部分还需要补充一下,不过就是按部就班填一填罢了。

下一步的设计思路,把注册中心、网关、OSS存储系统的服务给拉起来。

posted @ 2022-12-03 17:04  azxx  阅读(33)  评论(0编辑  收藏  举报