1、Vue登录加密填写用户名、密码,点击登录之后提交给后端;
2、后端接收密码,通过shiro实现身份验证;
3、将登录结果返回给前端。
1、前端将用户名、密码发给后端;
2、后端接收;
3、调用 subject.login 方法进行登录;
4、登录成功之后,生成token,返回给和前端;

身份验证,即在应用中谁能证明他就是他本人。一般提供如他们的身份 ID 一些标识信息来表明他就是他本人,如提供身份证,用户名 / 密码来证明。
在 shiro 中,用户需要提供
principals(身份)和credentials(证明)给 shiro,从而应用能验证用户身份:principals:身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。一个主体可以有多个
principals,但只有一个Primary principals,一般是用户名 / 密码 / 手机号。credentials:证明 / 凭证,即只有主体知道的安全值,如密码 / 数字证书等。
最常见的
principals和credentials组合就是用户名 / 密码了。接下来先进行一个基本的身份认证。另外两个相关的概念是之前提到的
Subject及Realm,分别是主体及验证主体的数据源。
1、JWT和Shiro依赖
- <!--shiro和springboot整合包-->
- <dependency>
- <groupId>org.apache.shiro</groupId>
- <artifactId>shiro-spring-boot-starter</artifactId>
- <version>1.6.0</version>
- </dependency>
- <!--jwt-->
- <dependency>
- <groupId>io.jsonwebtoken</groupId>
- <artifactId>jjwt</artifactId>
- <version>0.9.1</version>
- </dependency>
2、封装User实体类(本文只写关键字段,数据库根据实际情况设计)
- @Data
- @AllArgsConstructor
- @NoArgsConstructor
- public class User {
-
- private Integer id;
- // 用户唯一ID
- private String userID;
- // 用户密码
- private String userPassword;
- // 密码盐值
- private String userSalt;
- }
写一个从前端接收发送过来登陆信息的VO类(可不写,但是为了专业,尽量写)
- @Data
- @AllArgsConstructor
- @NoArgsConstructor
- /**
- * 接收从前端发过来的数据
- */
- public class UserVO {
-
- private String userID;
- private String userPassword;
- }
写一个登录成功之后,后端往前端发送封装数据的DTO类(可不写,但是为了专业,尽量写)
- @Data
- @AllArgsConstructor
- @NoArgsConstructor
- /**
- * 这个类主要封装登录成功之后,后台向前端发送的一些数据(可自定义)
- */
- public class UserDTO {
-
- // 用户ID
- private String userID;
- // 用户Role
- private String userRole;
- // 用户Token
- private String token;
- }
写一个封装Result,可以给前端返回一些标准数据(自定义)
- /**
- * controller返回类型
- *
- * @Return
- * code:代码
- * data:数据
- * msg:消息
- */
- @Data
- @AllArgsConstructor
- @NoArgsConstructor
- public class Result {
-
- private Integer code;
- private Object data;
- private String msg;
-
- }
这里的code可以自定义,放在一个类里面,方便以后调用(可自定义)
- /**
- * 各操作成功与否代码
- */
- public class Code {
-
- // 失败
- public static final Integer FAILED = 20000;
- // 成功
- public static final Integer SUCCESS = 20001;
- // 错误
- public static final Integer ERROR = 20002;
- // token失效
- public static final Integer TOKEN_INVALID = 40001;
- // 无权限
- public static final Integer NO_PERMISSION = 40002;
-
- }
3、关于User实体类的Mapper、Service根据自己所需要的自己定义,下文需要的基本方法有:
“通过userID获取User对象”。
- public interface UserService {
-
- /**
- * 通过用户ID获取User对象
- * @param userID
- * @return
- */
- User getUserByUserID(String userID);
- }
4、JWT工具类
主要包括两个参数和三个方法(可以按需求多写)
两个参数:token有效期和签名;
三个方法:创建token、解析token获取内容和验证token是否有效
- public class JwtUtil {
-
- // 时间毫秒数,token有效期(毫秒为单位)
- private static long time = 1000*60*60*24;
- // 签名(随便写,尽量复杂)
- private static final String signature = "signature123";
-
- // 创建Token
- public static String createToken(String userID, String userRole){
- JwtBuilder jwtBuilder = Jwts.builder();//构建JWT对象
- String jwtToken = jwtBuilder
- // Header
- .setHeaderParam("typ","JWT")
- .setHeaderParam("alg","HS256")
- // payload(可自定义)
- .claim("role",userRole)
- // 用户ID
- .setSubject(userID)
- // 设置有效期(毫秒单位)
- .setExpiration(new Date(System.currentTimeMillis()+time))
- .setId(UUID.randomUUID().toString())
- // signature
- .signWith(SignatureAlgorithm.HS256, signature)
- // compact拼接三部分header、payload、signature
- .compact();
- return jwtToken;
- }
-
- // 通过token获取UserID
- public static String getUserIDFromToken(String token){
- if(token == null){
- return "null";
- }
- try {
- JwtParser jwtParser = Jwts.parser();
- Jws
claimsJws = jwtParser.setSigningKey(signature).parseClaimsJws(token); - // System.out.println(claimsJws.getBody());
- return claimsJws.getBody().getSubject();
- } catch (Exception e) {//抛异常,说明token失效
- return "null";
- }
- }
-
- // 验证token是否有效
- public static Boolean checkToken(String token){
- if(token == null){
- return false;
- }
- try {
- JwtParser jwtParser = Jwts.parser();
- Jws
claimsJws = jwtParser.setSigningKey(signature).parseClaimsJws(token); - return true;
- }catch (Exception e){
- return false;
- }
- }
-
- }
5、Salt工具类,可以随机生成盐,并对密码进行加密
- public class SaltUtil {
-
- /**
- * 随机生成盐值工具
- * @param n
- * @return
- */
- public static String getSalt(int n){
- char[] chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890!@#$%^&*()".toCharArray();
- StringBuilder stringBuilder = new StringBuilder();
- for (int i = 0; i < n; i++) {
- char c = chars[new Random().nextInt(chars.length)];
- stringBuilder.append(c);
- }
- return stringBuilder.toString();
- }
-
- /**
- * 随机生成盐值,对用户密码进行加密
- * @param password
- * @return
- */
- public static List
encryption(String password){ -
- List
msg = new ArrayList<>(); - String salt = SaltUtil.getSalt(10);
- msg.add(salt);
-
- Md5Hash MD5 = new Md5Hash(password, salt, 1024);
- msg.add(MD5.toHex());
-
- return msg;
- }
- }
6、定义自己的Realm
Realm:域,
Shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色 / 权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。如我们之前的ini配置方式将使用org.apache.shiro.realm.text.IniRealm。
- /**
- * Shiro自定义Realm
- */
- public class MyRealm extends AuthorizingRealm {
-
- @Autowired
- private UserService userService;
-
- /**
- * 授权
- * @param principalCollection
- * @return
- */
- @Override
- protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
- return null;
- }
-
- /**
- * 认证
- * @param authenticationToken
- * @return
- * @throws AuthenticationException
- *
- * 客户端传来的 username 和 password 会自动封装到 token,先根据 username 进行查询,
- * 如果返回 null,则表示用户名错误,直接 return null 即可,Shiro 会自动抛出 UnknownAccountException 异常。
- * 如果返回不为 null,则表示用户名正确,再验证密码,直接返回 SimpleAuthenticationInfo 对象即可,
- * 如果密码验证成功,Shiro 认证通过,否则返回 IncorrectCredentialsException 异常。
- */
- @Override
- protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
- // System.out.println("启动认证");
- UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
- // 获取存放到数据库中的实体类
- User user = userService.getUserIDFromToken(token.getUserId());
- // System.out.println(user);
- if(user != null){
- // 参数列表(实体信息,密码,盐值,realm名称)
- return new SimpleAuthenticationInfo(user,user.getPassword(), ByteSource.Util.bytes(user.getSalt()),getName());
- }
- return null;
- }
- }
7、配置ShiroConfig
- /**
- * Shiro配置类
- */
- @Configuration
- public class ShiroConfig {
-
- /*
- * 倒序配置
- * 1、先自定义过滤器 MyRealm
- * 2、创建第二个DefaultWebSecurityManager,将MyRealm注入
- * 3、装配第三个ShiroFilterFactoryBean,将DefaultWebSecurityManager注入,并注入认证及授权规则
- * */
-
- //3、装配ShiroFilterFactoryBean,并将 DefaultWebSecurityManager 注入到 ShiroFilterFactoryBean 中
- @Bean
- public ShiroFilterFactoryBean factoryBean(DefaultWebSecurityManager manager){
- ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
- factoryBean.setSecurityManager(manager);//将 DefaultWebSecurityManager 注入到 ShiroFilterFactoryBean 中
- //注入认证及授权规则
- return factoryBean;
- }
-
- //2、创建DefaultWebSecurityManager ,并且将 MyRealm 注入到 DefaultWebSecurityManager bean 中
- @Bean
- public DefaultWebSecurityManager manager(MyRealm myRealm){
- DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
- manager.setRealm(myRealm);//将自定义的 MyRealm 注入到 DefaultWebSecurityManager bean 中
- return manager;
- }
-
- //1、自定义过滤器Realm
- @Bean
- public MyRealm myRealm(@Qualifier("hashedCredentialsMatcher") HashedCredentialsMatcher matcher){
- MyRealm myRealm = new MyRealm();
- // 密码匹配器
- myRealm.setCredentialsMatcher(matcher);
- return myRealm;
- }
-
- /**
- * 密码匹配器
- * @return HashedCredentialsMatcher
- */
- @Bean("hashedCredentialsMatcher")
- public HashedCredentialsMatcher hashedCredentialsMatcher(){
- HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
- // 设置哈希算法名称
- matcher.setHashAlgorithmName("MD5");
- // 设置哈希迭代次数
- matcher.setHashIterations(1024);
- // 设置存储凭证(true:十六进制编码,false:base64)
- matcher.setStoredCredentialsHexEncoded(true);
-
- return matcher;
- }
- }
写到这里Shiro的配置方面就写完了,接下来写用户登陆的controller类。
8、登录controller
- @PostMapping(value = "/login")
- public Result login(@RequestBody UserVo userVo){
- //System.out.println(userVo);
- Subject subject = SecurityUtils.getSubject();
- UsernamePasswordToken token = new UsernamePasswordToken(userVo.getUserID(), userVo.getPassword());
- try {
- subject.login(token);
- // 加一些东西放在token里面(可以自定义)
- String role = ...你获取role的service...;
- String JwtToken = JwtUtil.createToken(userVo.getUserID(), role);
- // 返回给前端的DTO
- UserDTO userDTO = new UserDTO(userVo.getUserID(),role,JwtToken);
- // 返回给前端的Result
- return Result(Code.SUCCESS, userDTO, "Success");
- }catch (UnknownAccountException e) {
- // 提示账户错误
- } catch (IncorrectCredentialsException e) {
- // 提示密码错误
- }
- }
界面代码,使用Vue3.0+Element Plus实现(简写,无style)
-
-
-
**系统
-
-
-
"form"> -
"username"> -
- v-model="form.userID"
- clearable
- placeholder="请输入账号"
- >
-
-
"password"> -
- v-model="form.userPassword"
- clearable
- placeholder="请输入密码"
- show-password
- >
-
-
-
-
-
"primary" @click.native.prevent="login()">登录 -
-
login方法
- login(){
- //console.log(this.form)
- axios.post("你的路径", this.form).then((res)=>{
- //console.log(res)
- if(res.data.code === 20001){
- // 可以将一些数据保存到本地localStorage
- localStorage.setItem("token",res.data.data.token);
- ElMessage({
- message: '登录成功!',
- type: 'success',
- })
- router.replace("登陆成功之后跳转的界面");
- }else {
- ElMessage({
- message: res.data.msg,
- type: 'error',
- })
- }
- })
- }
至此就登录功能就全部实现了。
六、总结
1、技术栈:
SpringBoot+JWT+MyBatis Plus+Shiro+Vue+Element Plus
2、身份验证步骤
-
收集用户身份 / 凭证,即如用户名 / 密码;
-
调用 Subject.login 进行登录,如果失败将得到相应的 AuthenticationException 异常,根据异常提示用户错误信息;否则登录成功;
- 最后调用
Subject.logout 进行退出操作
-
相关阅读:
JUC_8锁问题
【混合编程】C/C++调用Fortran的DLL
Windows Server 2012 R2系统服务器远程桌面服务多用户登录配置分享
WebService: SpringBoot集成WebService实践一
电脑技巧:Win10粘贴文件到C盘提示没有权限的解决方法
企业中WEB前端项目开发流程
密码学学习
【c++刷题Day2】专题3栈与队列&单调栈与单调队列
java---DFS算法---排列数字(每日一道算法2022.8.19)
H3CNE-构建中小企业网络全套培训PPT汇总【V7版本】
-
原文地址:https://blog.csdn.net/Hao_ge_666/article/details/126680167