一、关于密码加密
一般情况下选择Base64或者MD5加密,其中SpringSecurity中有BCryptPasswordEncoder类供密码加密,三者加密选择优先级如下:
BCryptPasswordEncoder>MD5>Base64
注:spring security中的BCryptPasswordEncoder方法采用SHA-256 +随机盐+密钥对密码进行加密。SHA系列是Hash算法,不是加密算法,使用加密算法意味着可以解密(这个与编码/解码一样),但是采用Hash处理,其过程是不可逆的。
每次加密后的密码都不一样,每次的随机盐都保存在加密后的密码中。在比较的时候,随机盐重新被取出。即加密后的密码中,前部分已经包含了盐信息。
二、关于JWT工具中密钥(盐)签名
1、当使用对称加密时考虑使用Jwts工具类,用户信息等可以在负载中通过链式调用直接加入进去。
a、加密
/**
* JWT工具类
* 注意点:
* 1.生成的token是可以通过base64进行解密出明文信息
* 2.base64进行解密出的明文信息,修改再进行编码,则会解密失败
* 3.无法作废已经颁布的令牌token,除非改密钥
*/
public class JWTUtils {
/**
* 过期时间为一周
*/
private static final long Expire=60000*60*24*7;
/**
* 密钥
*/
private static final String secret="xdclass.net168";
/**
* 令牌前缀
*/
private static final String Token_PreFix="xdclass";
/**
* subject主题
*/
private static final String SUBJECT="xdclass";
/**
* 根据用户信息生成令牌
* @param user
* @return
*/
public static String geneJsonWebToken(User user){
String token = Jwts.builder().setSubject(SUBJECT)
.claim("head_img", user.getHeadImg())
.claim("id", user.getId())
.claim("name", user.getName())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + Expire))
.signWith(SignatureAlgorithm.HS256, secret).compact();
token=Token_PreFix+token;
return token;
}
b、解密
public static Claims checkJWT(String token){
try{
final Claims claims = Jwts.parser().setSigningKey(secret)
.parseClaimsJws(token.replace(Token_PreFix, ""))
.getBody();
return claims;
}catch (Exception e){
return null;
}
}
2、当使用非对称加密时考虑使用JwtHelper工具类,其中用户等信息需单独通过数据结构存储后放入参数中
a、加密
Map<String,String> map=new HashMap<>();
map.put("userName", user.getUsername());
map.put("password", user.getPassword());
Jwt encode = JwtHelper.encode(JSON.toJSONString(map), new RsaSigner(rsaPrivateKey));
String encoded = encode.getEncoded();
b、解密
Jwt jwt = JwtHelper.decodeAndVerify(token, new RsaVerifier(publicKey));
String claims = jwt.getClaims();
System.out.println("claims"+claims);
return claims;
三、关于如何生成公私钥
公私钥生成主要有两种方式,一种是利用jdk自带的keytool工具生成,另外一种是通过代码生成,这里讲解代码生成方式
package com.dispart.dispartuser.Utils;
import org.apache.commons.codec.binary.Base64;
import sun.security.mscapi.CKeyPairGenerator;
import sun.security.pkcs.PKCS8Key;
import sun.security.util.DerValue;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.HashMap;
import java.util.Map;
public class GenerateKey {
// 加密算法
private static final String KEY_ALGORITHM = "RSA";
// 公钥key
private static final String PUB_KEY="publicKey";
// 私钥key
private static final String PRI_KEY="privateKey";
public static Map<String,String> generateKey() throws NoSuchAlgorithmException {
Map<String,String> keyMap=new HashMap<>();
KeyPairGenerator instance = KeyPairGenerator.getInstance(KEY_ALGORITHM);
KeyPair keyPair = instance.generateKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic();
//Base64 编码
byte[] privateKeyEncoded = privateKey.getEncoded();
String privateKeyStr = Base64.encodeBase64String(privateKeyEncoded);
byte[] publicKeyEncoded = publicKey.getEncoded();
String publicKeyStr=Base64.encodeBase64String(publicKeyEncoded);
keyMap.put(PUB_KEY,publicKeyStr);
keyMap.put(PRI_KEY,privateKeyStr);
return keyMap;
}
public static void main(String[] args) throws NoSuchAlgorithmException {
generateKey();
}
}
KeyPairGenerator工具类是springsecurity包中嵌套的类
如何将String类转换为RSAPublicKey以及RSAPrivateKey?
/**
* 得到公钥
*
* @param publicKey
* 密钥字符串(经过base64编码)
* @throws Exception
*/
public static RSAPublicKey getPublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
// 通过X509编码的Key指令获得公钥对象
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKey));
RSAPublicKey key = (RSAPublicKey) keyFactory.generatePublic(x509KeySpec);
return key;
}
/**
* 得到私钥pkcs8
*
* @param privateKey
* 密钥字符串(经过base64编码)
* @throws Exception
*/
public static RSAPrivateKey getPrivateKey(String privateKey)
throws NoSuchAlgorithmException, InvalidKeySpecException {
// 通过PKCS#8编码的Key指令获得私钥对象
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey));
RSAPrivateKey key = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);
return key;
}
四、SpringSecurity配置类配置
package com.example.springsecurityjwt.Config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true,jsr250Enabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
AuthenticationSuccess authenticationSuccess;
@Autowired
AuthenticationFail authenticationFail;
@Autowired
LogoutSuccess logoutSuccess;
@Autowired
myUserDeatil myUserDeatil;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/index","/register","/user/login","/user/fail").permitAll()//登录注册无需认证
.anyRequest().authenticated();//其余请求皆需认证
http.formLogin().loginPage("/index").loginProcessingUrl("/user/login").successForwardUrl("/user/login").failureUrl("/user/fail")
// .successHandler(authenticationSuccess).failureHandler(authenticationFail)
.and().logout().logoutSuccessHandler(logoutSuccess);
http.userDetailsService(myUserDeatil).csrf().disable();
}
// @Override
// protected void configure(HttpSecurity http) throws Exception {
// http.formLogin().loginPage("/login.html").loginProcessingUrl("/user/login").successForwardUrl("/user/index").permitAll()
// .and().authorizeRequests().anyRequest().authenticated()
// .and().csrf().disable();
// }
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDeatil).passwordEncoder(passwordEncoder);
}
public static void main(String[] args) {
String encode = new BCryptPasswordEncoder().encode("678");
System.out.printf( encode);
boolean bo = new BCryptPasswordEncoder().matches("678", "$2a$10$nm5LcfRw0y2c18FbpMulFef9o4tPZGSmKsZ1WvxMOSw34lEKILDIK");
System.out.println(bo);
}
}
细节:关于配置类中主要有两个大的模块配置
1.protected void configure(HttpSecurity http) throws Exception
重写方法中有几个点需重点注意,一是授权请求,二是登录页配置以及拦截请求处理方法。
http.userDetailsService(myUserDeatil)将用户信息注册进去,一定要配置
2.protected void configure(AuthenticationManagerBuilder auth) throws Exception
该方法将从数据库返回user对象指定加密方式和前端传回来的值进行匹配
五、user模块一定要实现public class User implements UserDetails接口
如果username和password和自定义的名称不一样,需重写get方法。
六、关于过滤器链的注意事项
.successHandler(authenticationSuccess).failureHandler(authenticationFail)
处理逻辑是在SpringSecurity自身的处理链之前,如果需要调用一定要完善其逻辑。
七、登录界面演示
输入正确的用户名以及密码
点击登录后,路由到成功/user/login界面
输入错误的用户名以及密码,路由到/user/fail失败界面
,