当你自己写一个登录页面的时候,你的密码没有加密会直接暴漏出来,那么我们就需要进行对你输入的密码进行加密
下面我就演示一下用ASE加密的方式来实现
一、前端加密
前端你需要一个key和vi你可以自定义,然后发送请求的时候,把这两个参数发过去
二、后端解密(基于Spring Security)
这是ASE工具类,key和vi必须和前端一致
package com.navimentum.officialWebsite.util;
import cn.hutool.core.util.HexUtil;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Objects;
/**
* =======================
* Introduce:
* User: Jiangjr
* Date: 2022/7/8
* ASE解密工具类
* =======================
*/
public class EncryptUtil {
// key:必须16个字符,且要和前端保持一致
private final static String KEY = "0123456789ASDFGH";
// 偏移量:必须16个字符,且要和前端保持一致
private final static String IV = "ASDFGH0123456789";
/**
* 将解密返回的数据转换成 String 类型
* @param content Base64编码的密文
*/
public static String decrypt(String content) {
return new String(Objects.requireNonNull(aesCbcDecrypt(parseHexStr2Byte(content), KEY.getBytes(), IV.getBytes())));
}
private static byte[] aesCbcEncrypt(byte[] content, byte[] keyBytes, byte[] iv) {
try {
SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
//设置模式,编码,后端为PKCS5Padding,对应前端是Pkcs7
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
return cipher.doFinal(content);
} catch (Exception e) {
System.out.println("exception:" + e.toString());
}
return null;
}
private static byte[] aesCbcDecrypt(byte[] content, byte[] keyBytes, byte[] iv) {
try {
SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
return cipher.doFinal(content);
} catch (Exception e) {
System.out.println("exception:" + e.toString());
}
return null;
}
/**
* 将16进制String转换为byte数组
*/
public static byte[] parseHexStr2Byte(String hexStr) {
if (hexStr.length() < 1) {
return null;
}
byte[] result = new byte[hexStr.length() / 2];
for (int i = 0; i < hexStr.length() / 2; i++) {
int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16);
int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2), 16);
result[i] = (byte) (high * 16 + low);
}
return result;
}
}
Security是基于DaoAuthenticationProvider类进行解密所以我们只需要重写里面的方法即可
package com.navimentum.officialWebsite.config.SecuriytConfig;
import com.navimentum.officialWebsite.service.Impl.UserDetailsServiceImpl;
import com.navimentum.officialWebsite.util.EncryptUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* =======================
* Introduce:
* User: Jiangjr
* Date: 2022/7/8
* ASE解密
* =======================
*/
public class MyAuthenticationProvider extends DaoAuthenticationProvider {
//UserDetailsServiceImpl是你获取数据库账号和密码的接口
public MyAuthenticationProvider(UserDetailsServiceImpl userDetailsService) {
setUserDetailsService(userDetailsService);
}
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
if (authentication.getCredentials() == null) {
this.logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
} else {
String presentedPassword = authentication.getCredentials().toString();
System.out.println("加密前密码"+presentedPassword );
// 对登录密码进行解密
presentedPassword = EncryptUtil.decrypt(presentedPassword);
System.out.println("解密后密码"+presentedPassword );
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {//前面是明文,后面是编码,判断是否一致
this.logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
}
}
这是我自己定义的UserDetailsServiceImpl要实现Security中的UserDetailsService,然后我自己建的mapper去查询数据库的用户和密码
package com.navimentum.officialWebsite.service.Impl;
import com.navimentum.officialWebsite.mapper.SysUserMapper;
import com.navimentum.officialWebsite.model.SysUser;
import com.navimentum.officialWebsite.util.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
/**
* =======================
* Introduce:
* User: Jiangjr
* Date: 2022/6/22
* =======================
*/
@Service(value = "userDetailsService")
@Transactional
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private SysUserMapper sysUserMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if (StringUtils.isEmpty(username)){
throw new RuntimeException("用户名不可为空");
}
//用户不为空,查询用户信息
SysUser sysUser = sysUserMapper.selectSysUserByUsername(username);
if (sysUser == null){
throw new UsernameNotFoundException(String.format("{}用户不存在",username));
}
//用户存在,返回用户信息
List<GrantedAuthority> grantedAuthorityList = new ArrayList<>();
//根据用户获取角色,获取权限集合
List<String> codeList = sysUserMapper.selectRoleCodeByUsername(username);
codeList.forEach(code->{
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(code);
grantedAuthorityList.add(authority);
});
return new User(sysUser.getUsername(),sysUser.getPassword(),grantedAuthorityList);
}
}
最后在SecurityConfig类里进行配置就OK了(SecurityConfig类是你自己写的关于Security的一些配置)
package com.navimentum.officialWebsite.config.SecuriytConfig;//package com.navimentum.officialWebsite.config.SecuriytConfig;
import com.navimentum.officialWebsite.service.Impl.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
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.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* =======================
* Introduce:
* User: Jiangjr
* Date: 2022/6/22
* =======================
*/
//@EnableWebSecurity //实现方法级别的安全配置 开启注解
//@EnableGlobalMethodSecurity(prePostEnabled = true) //实现方法级别的安全配置 开启注解
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
};
@Lazy
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private UserDetailsServiceImpl userDetailsServiceImpl;
/**
* 登录成功的处理
*/
@Autowired
private SuccessHandler successHandler;
/**
* 登录失败的处理
*/
@Autowired
private FailHandler failHandler;
/**
* 未登录的处理
*/
@Autowired
private MyAuthenticationEntryPoint myAuthenticationEntryPoint;
/**
* 未授权处理
*/
@Autowired
private MyAccessDeniedHandler myAccessDeniedHandler;
/**
* 注销处理器
*/
@Autowired
private MyLogoutHandler myLogoutHandler;
/**
* 注销成功
*/
@Autowired
private MyLogoutSuccessHandler myLogoutSuccessHandler;
/**
* 超时管理
*/
@Autowired
private MyInvalidSessionStrategy myInvalidSessionStrategy;
/**
* 被挤下线处理
*/
@Autowired
private MySessionInformationExpiredStrategy mySessionInformationExpiredStrategy;
/**
* 授权
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login").permitAll() // 放行/login的请求,但是用户密码必须正确
.antMatchers("/api") //访问/api需要什么权限和什么角色 可以注解实现 @PreAuthorize("hasAnyRole('panshAdmin')") or @PreAuthorize("hasAnyAuthority('ROLE_admin')")
.hasAnyAuthority("ROLE_admin")
// .antMatchers("/api/**")
// .hasAnyRole("panshAdmin")
// .anyRequest() //拦截所有请求
// .authenticated()
.and()
.logout().permitAll() //允许注销
.addLogoutHandler(myLogoutHandler)
.logoutSuccessHandler(myLogoutSuccessHandler)
.deleteCookies("JSESSIONID") //登出之后删除Cookie
.and()
.formLogin()
.loginProcessingUrl("/login")
.successHandler(successHandler) //登录成功返回消息
.failureHandler(failHandler) //登录失败返回消息
.and()
.exceptionHandling()
// .accessDeniedHandler(myAccessDeniedHandler)//权限不足处理
.authenticationEntryPoint(myAuthenticationEntryPoint)//未登录的处理
.and()
.authenticationProvider(new MyAuthenticationProvider(userDetailsServiceImpl));//接受前端加密的密码进行解码(ASE)
// .and()
// .sessionManagement()
// .invalidSessionStrategy(myInvalidSessionStrategy) //超时管理
// .sessionCreationPolicy(SessionCreationPolicy.STATELESS) //无状态的,任何状态下都不会生成session
// .maximumSessions(1) //最大可同时几个人登录
// .maxSessionsPreventsLogin(false) //是否允许登录
// .expiredSessionStrategy(mySessionInformationExpiredStrategy);//挤号处理
//关闭CSRF一般指跨站请求伪造
http.csrf().disable();
}
/**
* 认证
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}
}
主要是这里
以下是我数据库的关于Security的表和结构
sys_role表
sys_user表
sys_user_role表
如果你们只是需要登录,而不需要根据角色显示某个模块,就只需要sys_user表就可以实现。