邮箱登录 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}}

