十次方后端笔记六:微服务鉴权
使用 JWT 完成微服务鉴权

用户微服务

用户微服务密码加密存入数据库。

密码加密

准备工作

  1. 引入依赖

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
  2. 安全配置类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

    /**
    * 安全配置类
    */
    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    http
    .authorizeRequests()
    .antMatchers("/**").permitAll()
    .anyRequest().authenticated()
    .and().csrf().disable();
    }
    }
  3. 配置加密工具类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    package com.tensquare.user.config;

    import org.springframework.context.annotation.Bean;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.stereotype.Component;
    import util.IdWorker;

    /**
    * 存放所有用户微服务的bean
    */
    @Component
    public class UserApplicationConfig {

    @Bean
    public BCryptPasswordEncoder bcryptPasswordEncoder(){
    return new BCryptPasswordEncoder();
    }

    @Bean
    public IdWorker idWorker(){
    return new IdWorker(1, 8);
    }

    }

    这里我把UserApplication中的IdWorker的bean也放到这个类中。

管理员密码加密

  1. 修改AdminService#add的逻辑,对原始密码进行加密存储

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Autowired
    private BCryptPasswordEncoder encoder;
    /**
    * 增加
    * @param admin
    */
    public void add(Admin admin) {
    String decodePassword = idWorker.nextId()+"";
    //密码加密
    admin.setPassword(encoder.encode(decodePassword));
    adminDao.save(admin);
    }

管理员登录验证

  1. AdminController新增方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /**
    * 用户登陆
    */
    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public Result login(@RequestBody Map<String, String> loginMap) {
    Admin admin = adminService.findByLoginnameAndPassword(loginMap.get("loginname"), loginMap.get("password"));
    if (admin != null) {
    return new Result(true, StatusCode.OK, "登陆成功");
    }
    return new Result(false, StatusCode.LOGINERROR, "用户名或密码错误");
    }
  2. AdminService新增方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    /**
    * 根据登陆名和密码查询
    *
    * @param loginname
    * @param password
    * @return
    */
    public Admin findByLoginnameAndPassword(String loginname, String password){
    Admin admin = adminDao.findByLoginname(loginname);
    if( admin!=null && encoder.matches(password,admin.getPassword())) {
    return admin;
    }
    return null;
    }
  3. AdminDao新增方法

    1
    Admin findByLoginname(String loginname);

用户密码加密

  1. 修改UserService#add(User user, String code)方法,对原始密码进行加密存储

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    /**
    * 增加
    *
    * @param user 用户
    * @param code 用户填写的验证码
    */
    public void add(User user, String code) {
    //判断验证码是否正确
    String syscode = (String) redisTemplate.opsForValue().get("smscode_" + user.getMobile());
    //提取系统正确的验证码
    if (syscode == null) {
    throw new RuntimeException("请点击获取短信验证码");
    }
    if (!syscode.equals(code)) {
    throw new RuntimeException("验证码输入不正确");
    }
    user.setId(idWorker.nextId() + "");
    user.setFollowcount(0);//关注数
    user.setFanscount(0);//粉丝数
    user.setOnline(0L);//在线时长
    user.setRegdate(new Date());//注册日期
    user.setUpdatedate(new Date());//更新日期
    user.setLastdate(new Date());//最后登陆日期

    //密码加密
    user.setPassword(encoder.encode(user.getPassword()));

    userDao.save(user);
    }

用户登录验证

  1. UserController新增方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /**
    * 用户登陆
    */
    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public Result login(String mobile, String password) {
    User user = userService.findByMobileAndPassword(mobile, password);
    if (user != null) {
    return new Result(true, StatusCode.OK, "登陆成功");
    }
    return new Result(false, StatusCode.LOGINERROR, "用户名或密码错误");
    }
  2. UserService新增方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    /**
    * 根据手机号和密码查询用户
    *
    * @param mobile
    * @param password
    * @return
    */
    public User findByMobileAndPassword(String mobile, String password) {
    User user = userDao.findByMobile(mobile);
    if (user != null && encoder.matches(password, user.getPassword())) {
    return user;
    }
    return null;
    }
  3. UserDao新增方法

    1
    User findByMobile(String mobile);

鉴权微服务

鉴权微服务创建Module(省略)

准备工作

鉴权工具类

tensquare_common中引入JWT依赖并编写工具类

  1. 引入JWT依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.6.0</version>
    </dependency>
  2. 编写工具类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    package util;

    import io.jsonwebtoken.Claims;
    import io.jsonwebtoken.JwtBuilder;
    import io.jsonwebtoken.Jwts;
    import io.jsonwebtoken.SignatureAlgorithm;
    import lombok.Data;
    import org.springframework.boot.context.properties.ConfigurationProperties;

    import java.util.Date;

    @Data
    @ConfigurationProperties("jwt.config")
    public class JwtUtil {

    private String key ;
    private long ttl ;// token有效期

    /**
    * 生成JWT
    *
    * @param id
    * @param subject
    * @return
    */
    public String createJWT(String id, String subject, String roles) {
    long nowMillis = System.currentTimeMillis();
    Date now = new Date(nowMillis);
    JwtBuilder builder = Jwts.builder().setId(id)
    .setSubject(subject)
    .setIssuedAt(now)
    .signWith(SignatureAlgorithm.HS256, key).claim("roles", roles);
    if (ttl > 0) {
    builder.setExpiration( new Date( nowMillis + ttl));
    }
    return builder.compact();
    }

    /**
    * 解析JWT
    * @param jwtStr
    * @return
    */
    public Claims parseJWT(String jwtStr){
    return Jwts.parser()
    .setSigningKey(key)
    .parseClaimsJws(jwtStr)
    .getBody();
    }
    }

管理员后台登陆签发token

配置Jwt工具类Bean

UserApplicationConig中配置Bean

1
2
3
4
@Bean
public JwtUtil jwtUtil(){
return new JwtUtil();
}

配置JWT常量

在用户微服务中的application.yml中配置相关参数

1
2
3
4
jwt:
config:
key: imxushuai
ttl: 360000

修改AdminController#login方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 用户登陆
*/
@RequestMapping(value = "/login", method = RequestMethod.POST)
public Result login(@RequestBody Map<String, String> loginMap) {
Admin admin = adminService.findByLoginnameAndPassword(loginMap.get("loginname"), loginMap.get("password"));
if (admin != null) {
//生成token
String token = jwtUtil.createJWT(admin.getId(), admin.getLoginname(), "admin");
Map<String, String> map = new HashMap<>();
map.put("token", token);
map.put("name", admin.getLoginname());//登陆名
return new Result(true, StatusCode.OK, "登陆成功", map);
}
return new Result(false, StatusCode.LOGINERROR, "用户名或密码错误");
}

统一鉴权拦截器

编写拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.tensquare.user.filter;

import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import util.JwtUtil;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class JwtFilter extends HandlerInterceptorAdapter {

@Autowired
private JwtUtil jwtUtil;

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
final String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
final String token = authHeader.substring(7);
Claims claims = jwtUtil.parseJWT(token);
if (claims != null) {
if("admin".equals(claims.get("roles"))){//如果是管理员
request.setAttribute("admin_claims", claims);
}
if("user".equals(claims.get("roles"))){//如果是用户
request.setAttribute("user_claims", claims);
}
}
}
return true;
}
}

拦截器中只是对token进行解析,只要解析成功就放心,具体的操作权限交给具体的业务进行判断。

比如:删除用户

​ 使用admin的token,在拦截器中会在Request中添加keyadmin_claims的数据,在执行到具体的controller当中,只需要判断是否含有admin_claimskey即可,含有该key就执行逻辑,否则就抛出权限不足异常。

配置拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.tensquare.user.config;

import com.tensquare.user.filter.JwtFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

@Configuration
public class ApplicationConfig extends WebMvcConfigurationSupport {

@Autowired
private JwtFilter jwtFilter;

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

用户登录生成token

修改UserController#login方法逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
   @Autowired
private JwtUtil jwtUtil;
/**
* 用户登陆
*/
@RequestMapping(value = "/login", method = RequestMethod.POST)
public Result login(@RequestBody Map<String, String> loginMap) {
User user = userService.findByMobileAndPassword(loginMap.get("mobile"), loginMap.get("password"));
if (user != null) {
String token = jwtUtil.createJWT(user.getId(),
user.getNickname(), "user");
Map<String, String> map = new HashMap<>();
map.put("token", token);
map.put("name", user.getNickname());//昵称
map.put("avatar", user.getAvatar());//头像
return new Result(true, StatusCode.OK, "登陆成功", map);
}
return new Result(false, StatusCode.LOGINERROR, "用户名或密码错误");
}

各微服务鉴权添加(省略)

文章作者: imxushuai
文章链接: https://www.imxushuai.com/2002/01/02/6.十次方后端笔记六:微服务鉴权/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 imxushuai
支付宝打赏
微信打赏