spring security 2

242 阅读5分钟

邮箱登录 token验证

maven 引入数据库,邮件,jwt,基础模块引入spring security 1的项目
github.com/cailon
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.11.2</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.11.2</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
            <version>0.11.2</version>
            <scope>runtime</scope>
        </dependency>
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test?createDatabaseIfNotExist=true&useUnicode=true&serverTimezone=GMT%2B8
    username: root
    password: 123456
  jpa:
    properties:
      hibernate:
        hbm2ddl:
          auto: update
        dialect: org.hibernate.dialect.MySQL5InnoDBDialect
        format_sql: true
    show-sql: false
  servlet:
    multipart:
      enabled: true
      file-size-threshold: 2KB
      max-file-size: 5MB
      max-request-size: 40MB
  mail:
    host: smtp.qq.com
    username: cailonghao1234@foxmail.com
    password: 开通pop3设置的密码
    port: 25

设置security为无状态,开放一个注册和一个发送邮箱验证码的接口


                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS) #无状态
                .and()
                .authorizeRequests()
                .mvcMatchers("/open/**").permitAll() #公开的路径
                .anyRequest().authenticated()
                .httpBasic().disable()
                .csrf().disable();

邮件(云服务发邮件要申请开通25端口)和注册

QQ邮箱开通相关服务

邮箱工具类 存储验证码(存map里) ,发送邮件

@Component
public class EmailUtil {
    @Value("${spring.mail.username}")
    private static String email;
    @Resource
    private  JavaMailSender javaMailSender;

    public static Map<String, Object> codes = new HashMap<>();

    /**
     * @exception:
     * @DESP: 简单邮件(纯文字)
     * @Date: 2020/6/16 cai
     */
    public Boolean sendEmail(String email) {
        String code = getCoge();
        SimpleMailMessage mail = new SimpleMailMessage();
        mail.setTo(email);
        mail.setFrom(email);
        mail.setSubject("邮箱验证码");
        mail.setText(code);
        javaMailSender.send(mail);
        codes.put(email, code);
        return true;
    }

    /**
     * 获取随机六个字符
     */
    public String getCoge() {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < 6; i++) {
            char c = (char) (int) (Math.random() * 26 + 97);
            sb.append(c);
        }
        return sb.toString();
    }

    public static String getEmail() {
        return email;
    }

    public static void setEmail(String email) {
        EmailUtil.email = email;
    }
}

编写注册和发送邮件

 /**
     * @exception:
     * @DESP: 发送邮件
     * @Date: 2020/6/16 cai
     */
    @RequestMapping("/open/sendEmail")
    public Map<String, Object> sendMail(HttpServletRequest request,
                                        @RequestParam String email) {
        emailUtil.sendEmail(email);
        Map<String, Object> map = new HashMap<>();
        map.put("msg", "发送成功");
        map.put("uri", request.getRequestURI());
        return map;
    }

    /**
     * @exception:
     * @DESP: 注册
     * @Date: 2020/6/16 cai
     */
    @RequestMapping("/open/register")
    public Map<String, Object> register(HttpServletRequest request,
                                        @RequestParam String email,
                                        @RequestParam String code,
                                        @RequestParam String password) {
        Map<String, Object> map = new HashMap<>();
        try {
            String tempCode = (String) EmailUtil.codes.get(email);
            if (code.equals(tempCode)) {
                String newPass = passwordEncoder.encode(password);
                Person person = new Person(email, newPass, "ROLE_ADMIN");
                personRepository.save(person);
                log.info("注册成功:{}", person);
                map.put("msg", "创建成功");
            }
        } catch (Exception e) {
            map.put("msg", "创建失败");
        }
        map.put("uri", request.getRequestURI());
        return map;
    }

验证登录

AuthenticationManager是委托AuthenticationProvider去实现用户的验证的,可以继承AbstractUserDetailsAuthenticationProvider(继承AuthenticationProvider)编写自己的验证机制

邮箱验证

@Slf4j
@Component
public class AppAuthentication extends AbstractUserDetailsAuthenticationProvider {

    @Autowired
    PasswordEncoder passwordEncoder;
    @Autowired
    LoadUserService loadUserService;

    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) throws AuthenticationException {
        log.info("邮件认证");
        if (userDetails == null) {
            throw new UsernameNotFoundException("认证失败");
        }
        String email = userDetails.getUsername();
        String tempCode = (String) EmailUtil.codes.get(email);
        String code = (String) usernamePasswordAuthenticationToken.getCredentials();
        log.info("code:{}", code);
//        if (!(code.equals(tempCode))) {
//            throw new UsernameNotFoundException("认证失败");
//        }
    }

    @Override
    protected UserDetails retrieveUser(String s, UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) throws AuthenticationException {
        return loadUserService.loadUserByUsername(s);
    }
}

对比项

@Slf4j
@Component
public class WxAuthentication extends AbstractUserDetailsAuthenticationProvider {
    @Autowired
    PasswordEncoder passwordEncoder;
    @Autowired
    LoadUserService loadUserService;
    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) throws AuthenticationException {
        log.info("wx 自定义的认证方式");
        throw new UsernameNotFoundException("认证失败");
    }

    @Override
    protected UserDetails retrieveUser(String s, UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) throws AuthenticationException {
        return loadUserService.loadUserByUsername(s);
    }
}

SecurityConfig添加验证

@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(wxAuthentication);
        auth.authenticationProvider(appAuthentication);
    }
实体类要继承UserDetails,
@Setter
@Getter
@Entity
@Table
public class Person implements UserDetails {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String email;
    private String password;
    private String roles;
    @Transient
    private Collection<GrantedAuthority> collection;

    public Person() {
    }

    public Person(String email, String password, String... args) {
        this.email = email;
        this.password = password;
        List<GrantedAuthority> list = new ArrayList<>();
        String roles = "";
        for (int i = 0; i < args.length; i++) {
            String role = args[i];
            list.add((GrantedAuthority) () -> role);
            roles += role + ",";
        }
        this.collection = list;
        this.roles = roles;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        if (collection == null) {
            List<GrantedAuthority> list = new ArrayList<>();
            String[] rs = this.roles.split(",");
            for (int i = 0; i < rs.length; i++) {
                String role = rs[i];
                list.add((GrantedAuthority) () -> role);
            }
            this.collection = list;
        }
        return collection;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return email;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    @Override
    public String toString() {
        return "Person{" +
                "email='" + email + '\'' +
                ", password='" + password + '\'' +
                ", collection=" + collection +
                '}';
    }
}

jwt工具类 解/编 jwt ,

@Slf4j
public final class Jwtutil {

    private static final Long expire = 3600000L; # 过期时间 一小时
    private static final String secret = "myappsecret+++"; # 生成
    private static final String owner = "myapp";
    private static final SecretKey secretKey = AesUtil.getSecretkey(secret,"HmacSHA256");

    /**
     * @exception:
     * @DESP: 获取jwt
     * @Date: 2020/6/16 cai
     */
    public static String getJwt(Long id, String username) {
        JwtBuilder jwt = Jwts.builder()
                .setId(String.valueOf(id)) #放过用户Id
                .setIssuer(username) #用户名
                .setSubject(owner) #签发人
                .setExpiration(new Date(expire+System.currentTimeMillis())) #过期时间
                .setIssuedAt(new Date()) #签发时间
                .signWith(secretKey); #密钥
        return jwt.compact();
    }

    /**
     * @exception:
     * @DESP: 解析token
     * @Date: 2020/6/16 cai
     */
    public static Claims parseToken(String token) {
        Jws<Claims> jws;
        try {
            jws = Jwts.parserBuilder()
                    .setSigningKey(secretKey)
                    .build()
                    .parseClaimsJws(token);
        } catch (JwtException e) {
            log.error("token解析错误:{}",e.getMessage());
            return null;
        }
        return jws.getBody();
    }
}
//------------生成256位的密钥-参数-加密数据-加密方式---------
 public static SecretKey getSecretkey(String secret,String model) {
        try {
            KeyGenerator aes = KeyGenerator.getInstance(model);
            aes.init(256);
            SecretKey secretKey = aes.generateKey();
            return secretKey;
        } catch (Exception e) {
            return null;
        }

    }

获取请求头里的token,需要添加一个指定的filters处理相关请求

@Slf4j
@Order(0)
@Component
public class TokenFilter extends OncePerRequestFilter {
    @Autowired
    PersonRepository personRepository;
    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        log.info("token filter");
        String token = httpServletRequest.getHeader("token");
        if (!(StringUtils.isEmpty(token))) {
            Claims claims = Jwtutil.parseToken(token);
            if (claims != null) {
                String username = claims.getIssuer();
                Person person = personRepository.findPersonByEmail(username);
                UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(username,"",person.getAuthorities());
                SecurityContextHolder.getContext().setAuthentication(auth); #绑定用户数据
            }
        }
        filterChain.doFilter(httpServletRequest, httpServletResponse);
    }
}

securityConfig添加filter

 .and()
 .addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class)

注册(自己给自己发邮件)

### 发送邮件
POST http://localhost:34000/open/sendEmail
Content-Type: application/x-www-form-urlencoded

email=cailonghao1234@foxmail.com
### 注册
POST http://localhost:34000/open/register
Content-Type: application/x-www-form-urlencoded

email=cailonghao1234@foxmail.com&password=123456&code=bboqvg

登录 验证

### 获取token
POST http://localhost:34000/login
Content-Type: application/x-www-form-urlencoded

email=cailonghao1234@foxmail.com&password=bboqvg

> {% client.global.set("token", response.body.token); %}

### 验证token
POST http://localhost:34000/user
token:{{token}}