SpringBoot项目(五) --- 配置MySql与注册登录界面

1.集成jwt验证

session模式:

某些url是公开的,如login、registe,他们不需要让用户登录。登陆成功后生成session到服务器端。(多个服务器的话就要存多个)

其余的url是不公开的,需要用户先登录才能访问。要判断session是否有效,然后将session当中的user信息提取到上下文中,再访问controller。

jwt验证方式:方便前后端分离的跨域问题而且jwt-token是存在于本地浏览器中

加入依赖:jjwt-api、jjwt-impl、jjwt-jackson

		<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api -->
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt-api</artifactId>
			<version>0.11.5</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl -->
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt-impl</artifactId>
			<version>0.11.5</version>
			<scope>runtime</scope>
		</dependency>
		<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-jackson -->
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt-jackson</artifactId>
			<version>0.11.5</version>
			<scope>runtime</scope>
		</dependency>

实现utils.JwtUtil类,为jwt工具类,用来创建、解析jwt token

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;

@Component
public class JwtUtil {
    public static final long JWT_TTL = 60 * 60 * 1000L * 24 * 14;  // 有效期14天
    public static final String JWT_KEY = "SDFGjhdsfalshdfHFdsjkdsfds121232131afasdfac";

    public static String getUUID() {
        return UUID.randomUUID().toString().replaceAll("-", "");
    }

    public static String createJWT(String subject) {
        JwtBuilder builder = getJwtBuilder(subject, null, getUUID());
        return builder.compact();
    }

    private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        SecretKey secretKey = generalKey();
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        if (ttlMillis == null) {
            ttlMillis = JwtUtil.JWT_TTL;
        }

        long expMillis = nowMillis + ttlMillis;
        Date expDate = new Date(expMillis);
        return Jwts.builder()
                .setId(uuid)
                .setSubject(subject)
                .setIssuer("sg")
                .setIssuedAt(now)
                .signWith(signatureAlgorithm, secretKey)
                .setExpiration(expDate);
    }

    public static SecretKey generalKey() {
        byte[] encodeKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
        return new SecretKeySpec(encodeKey, 0, encodeKey.length, "HmacSHA256");
    }

    public static Claims parseJWT(String jwt) throws Exception {
        SecretKey secretKey = generalKey();
        return Jwts.parserBuilder()
                .setSigningKey(secretKey)
                .build()
                .parseClaimsJws(jwt)
                .getBody();
    }
}

实现config.filter.JwtAuthenticationTokenFilter类,用来验证jwt token,如果验证成功,则将User信息注入上下文中

import com.kob.backend.mapper.UserMapper;
import com.kob.backend.pojo.User;
import com.kob.backend.service.impl.utils.UserDetailsImpl;
import com.kob.backend.utils.JwtUtil;
import io.jsonwebtoken.Claims;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;


import java.io.IOException;

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    @Autowired
    private UserMapper userMapper;

    @Override
    protected void doFilterInternal(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException {
        String token = request.getHeader("Authorization");

        if (!StringUtils.hasText(token) || !token.startsWith("Bearer ")) {
            filterChain.doFilter(request, response);
            return;
        }

        token = token.substring(7);

        String userid;
        try {
            Claims claims = JwtUtil.parseJWT(token);
            userid = claims.getSubject();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        User user = userMapper.selectById(Integer.parseInt(userid));

        if (user == null) {
            throw new RuntimeException("用户名未登录");
        }

        UserDetailsImpl loginUser = new UserDetailsImpl(user);
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(loginUser, null, null);

        SecurityContextHolder.getContext().setAuthentication(authenticationToken);

        filterChain.doFilter(request, response);
    }
}

配置config.SecurityConfig类,放行登录、注册等接口

import com.kob.backend.config.filter.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
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.configurers.CsrfConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
        return authConfig.getAuthenticationManager();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf(CsrfConfigurer::disable) // 基于token,不需要csrf
                .sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 基于token,不需要session
                .authorizeHttpRequests((authz) -> authz
                        .requestMatchers("/user/account/token", "/user/account/register").permitAll() // 放行api
                        .requestMatchers(HttpMethod.OPTIONS).permitAll()
                        .anyRequest().authenticated()
                )
                .addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }
}

2.创建后端api

将数据库中的id域变为自增
在pojo.User类中添加注解:@TableId(type = IdType.AUTO)
实现/user/account/token/:验证用户名密码,验证成功后返回jwt token(令牌)
实现/user/account/info/:根据令牌返回用户信息
实现/user/account/register/:注册账号

2.1实现登录功能

package com.kob.backend.service.impl.user.account;

import com.kob.backend.pojo.User;
import com.kob.backend.service.impl.utils.UserDetailsImpl;
import com.kob.backend.service.user.account.LoginService;
import com.kob.backend.utils.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

@Service
public class LoginServiceImpl implements LoginService {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Override
    public Map<String, String> getToken(String username, String password) {
        UsernamePasswordAuthenticationToken token =
                new UsernamePasswordAuthenticationToken(username, password);
        Authentication authenticate = authenticationManager.authenticate(token); //这里如果登录失败,会自动报错

        UserDetailsImpl loginUser = (UserDetailsImpl) authenticate.getPrincipal();
        User user = loginUser.getUser();
        String jwt = JwtUtil.createJWT(user.getId().toString());

        Map<String, String> map = new HashMap<String, String>();;
        map.put("error_message","success");  //如果执行到这里一定是成功的
        map.put("token",jwt);
        return map;
    }
}


//可以这样根据失败结果返回给用户
try {
    Authentication authenticate = authenticationManager.authenticate(token);
    // 登录成功的处理逻辑
} catch (AuthenticationException e) {
    // 登录失败的处理逻辑
    // 根据异常类型进行适当的处理
    if (e instanceof BadCredentialsException) {
        // 处理用户名或密码错误
    } else if (e instanceof DisabledException) {
        // 处理用户被禁用
    } else if (e instanceof LockedException) {
        // 处理用户被锁定
    } else if (e instanceof AccountExpiredException) {
        // 处理用户帐户已过期
    } else if (e instanceof CredentialsExpiredException) {
        // 处理用户凭据(密码)已过期
    } else {
        // 其他类型的身份验证异常
    }
}

@RestController
public class LoginController {
    @Autowired
    LoginService loginService;

    @PostMapping("/user/account/token/")
    public Map<String,String> getToken(@RequestParam Map<String,String> map) {
        String username = map.get("username");
        String password = map.get("password");
        return loginService.getToken(username,password);
    }
}

2.2.实现注册功能

@Service
public class RegisterServiceImpl implements RegisterService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    PasswordEncoder passwordEncoder;

    @Override
    public Map<String, String> register(String username, String password, String confirmedPassword) {
        Map<String,String> map = new HashMap<String,String>();

        if(username == null) {
            map.put("error_message","用户名不能为空");
            return map;
        }
        if(password == null || confirmedPassword == null) {
            map.put("error_message","密码或者确认密码不能为空");
            return map;
        }
        if(!password.equals(confirmedPassword)) {
            map.put("error_message","两次密码不一致");
            return map;
        }

        username = username.trim();
        if(username.length() == 0) {
            map.put("error_message","用户名不能为空");
            return map;
        }
        if(username.length() >  100) {
            map.put("error_message","用户名长度不能大于100");
            return map;
        }
        if(password.length() >  100 || confirmedPassword.length() > 100) {
            map.put("error_message","密码长度不能大于100");
            return map;
        }
        if (password.length() == 0 || confirmedPassword.length() == 0) {
            map.put("error_message","密码不能为空");
            return map;
        }

        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("username", username);
        if(userMapper.selectCount(queryWrapper) > 0) {
            map.put("error_message","用户名重复");
            return map;
        }

        String encodedPassword = passwordEncoder.encode(password);
        User user = new User(null,username,encodedPassword,"https://cdn.acwing.com/media/user/profile/photo/143543_lg_7ef77402b7.jpg");
        userMapper.insert(user);

        map.put("error_message","注册用户成功");
        return map;
    }
}
@RestController
public class RegisterController {

    @Autowired
    private RegisterService registerService;

    @PostMapping("/user/account/register/")
    public Map<String,String> register(@RequestParam Map<String,String> map) {
        String username = map.get("username");
        String password = map.get("password");
        String confirmedPassword = map.get("confirmedPassword");
        System.out.println("Asd");
        return registerService.register(username,password,confirmedPassword);
    }
}

 2.3获取用户信息的功能

@Service
public class InfoServiceImpl implements InfoService {

    @Override
    public Map<String, String> getInfo() {
        //上下文当中提取用户信息
        UsernamePasswordAuthenticationToken authenticationToken =
                (UsernamePasswordAuthenticationToken)SecurityContextHolder.getContext().getAuthentication();

        UserDetailsImpl loginUser = (UserDetailsImpl)authenticationToken.getPrincipal();
        User user = loginUser.getUser();

        Map<String, String> info = new HashMap<String, String>();
        info.put("error_message","success");
        info.put("id",user.getId().toString());
        info.put("username",user.getUsername());
        info.put("photo",user.getPhoto());

        return info;
    }
}

@RestController
public class InfoController {

    @Autowired
    private InfoService infoService;

    @GetMapping("/user/account/info/")
    public Map<String,String> getInfo() {
        return infoService.getInfo();
    }
}

 2.4前端向后端发送的请求

setup() {
    $.ajax({
      url: "http://127.0.0.1:3000/user/account/token/",
      type: "post",
      data: {
        username: 'cjb',
        password: 'pcjb',
      },
      success(resp) {
        console.log(resp);
      },
      error(resp) {
        console.log(resp);
      }
    });

    $.ajax({
      url: "http://127.0.0.1:3000/user/account/info/",
      type: "get",
      headers: {
        Authorization:"Bearer " + "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJjZjE4ZTU5OWE4MTM0MGEzOWMwN2E4ZjY2OGQ4YmE2MSIsInN1YiI6IjEiLCJpc3MiOiJzZyIsImlhdCI6MTcxMzM1MzMxNCwiZXhwIjoxNzE0NTYyOTE0fQ.3h6I4bbuX44Jy5ReCi_i6VPbtPZFhXXcGDhmHPy5eCw"
      },
      success(resp) {
        console.log(resp);
      },
      error(resp) {
        console.log(resp);
      }
    });

    $.ajax({
      url: "http://127.0.0.1:3000/user/account/register/",
      type: "post",
      data: {
        username: "e",
        password: "pe",
        confirmedPassword: "pe",
      },
      success(resp) {
        console.log(resp);
      },
      error(resp) {
        console.log(resp);
      }
    })

3.前端界面

使用vuex当中的store维护全局状态,如用户id、用户名、photo、token信息、是否登录is_login;

当然这些信息写在store文件下的user.js文件夹里面,然后把user.js抛出,index.js引入user.js。

其中user.js里面的mutations里面写直接修改全局变量的方法,供给actions里面的函数进行使用,这里使用context.commit()进行调用的。

actions里面的函数是供给外面的js使用的,使用store.dispatch()进行调用。

import { createStore } from 'vuex'
import UserModule from './user'

export default createStore({
  state: {
  },
  getters: {
  },
  mutations: {
  },
  actions: {
  },
  modules: {
    user: UserModule
  }
})
import $ from 'jquery'

export default {
    state: {  //全局信息
        id:"",
        username:"",
        photo:"",
        token:"",
        is_login:false
    },
    getters: {  
    },
    mutations: {  //修改信息
        updateUser(state,user) {
            state.id = user.id,
            state.username = user.username,
            state.photo = user.photo,
            state.is_login = user.is_login
        },
        updateToken(state,token) {
            state.token = token
        },
        logout(state) {
          state.id = "",
          state.username = "",
          state.photo = "",
          state.token = "",
          state.is_login = false
        }
    },
    actions: {
        login(context,data) {
            $.ajax({
                url: "http://127.0.0.1:3000/user/account/token/",
                type: "post",
                data: {
                  username: data.username,
                  password: data.password,
                },
                success(resp) {
                  if(resp.error_message === 'success') {
                    context.commit("updateToken",resp.token);
                    data.success(resp);
                  } else {
                    data.error(resp);
                  }
                },
                error(resp) {
                  data.error(resp);
                }
              });
        },

        getinfo(context,data) {
            $.ajax({
              url: "http://127.0.0.1:3000/user/account/info/",
              type: "get",
              headers: {
                Authorization:"Bearer " + context.state.token,
              },
              success(resp) {
                if(resp.error_message === 'success') {
                  context.commit("updateUser",{
                    ...resp,    //解析resp的内容
                    is_login: true,
                  });
                  data.success(resp);
                } else {
                  data.error(resp);
                }
              },
              error(resp) {
                data.error(resp);
              }
            });
        },

        logout(context) {
          context.commit("logout");
        },

    },
    modules: {
    }
}

navbar的修改:已登录显示用户名,未登录显示登录注册界面。这里就要用store里面的is_login来进行判断了。

<ul class="navbar-nav" v-if="$store.state.user.is_login">
        <li class="nav-item dropdown">
          <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
            {{ $store.state.user.username }}
          </a>
          <ul class="dropdown-menu">
            <li>
                <router-link class="dropdown-item" :to="{name:'user_bot_index'}">我的Bots</router-link>
            </li>
            <li><hr class="dropdown-divider"></li>
            <li><a class="dropdown-item" href="#" @click="logout">退出</a></li>
          </ul>
        </li>
      </ul>
      <ul class="navbar-nav" v-else>
        <li class="nav-item dropdown">
          <router-link class="nav-link " :to="{name : 'user_account_register'}" role="button">
            注册
          </router-link>
        </li>
        <li>
          <router-link class="nav-link " :to="{name : 'user_account_login'}" role="button">
            登录
          </router-link>
        </li>
      </ul>

退出函数:清空user.js里面变量的值。

4.改进

4.1强制让用户登录,不登陆访问不了其余界面,即前端页面授权

使用router机制来实现,在每个路由上加上属性meta { requestAuth:true/false},表示是否需要收授权。使用router的beforeEach函数来实现授权,在里面判断是否需要授权并且是否登录?

import { createRouter, createWebHistory } from 'vue-router'

import PkIndexView from '../views/pk/PkIndexView'
import RankListIndexView from '../views/ranklist/RankListView'
import RecordIndexView from '../views/record/RecordIndexView'
import UserBotIndexView from '../views/user/bot/UserBotIndexView'
import NotFound from '../views/error/NotFound'
import UserAccountLoginView from '../views/user/account/UserAccountLoginView'
import UserAccountRegisterView from '../views/user/account/UserAccountRegisterView'
import store from '../store/user'

const routes = [
  //默认路由是pk界面
  {
    path:"/",
    name:"home",
    redirect:"/pk/",
    meta: {
      requestAuth: true    //是否需要登录授权才能访问
    }
  },
  {
    path:"/pk/",
    name:"pk_index",
    component:PkIndexView,
    meta: {
      requestAuth: true  
    }
  },
  {
    path:"/record/",
    name:"record_index",
    component:RecordIndexView,
    meta: {
      requestAuth: true  
    }
  },
  {
    path:"/ranklist/",
    name:"ranklist_index",
    component:RankListIndexView,
    meta: {
      requestAuth: true  
    }
  },
  {
    path:"/user/bot/",
    name:"user_bot_index",
    component:UserBotIndexView,
    meta: {
      requestAuth: true  
    }
  },
  {
    path:"/user/account/login/",
    name:"user_account_login",
    component: UserAccountLoginView,
    meta: {
      requestAuth: false  
    }
  },
  {
    path:"/user/account/register/",
    name:"user_account_register",
    component: UserAccountRegisterView,
    meta: {
      requestAuth: false  
    }
  },
  {
    path:"/404/",
    name:"404",
    component:NotFound,
    meta: {
      requestAuth: false  
    }
  },
  {
    //匹配其他所有的非法的url
    path:"/:catchAll(.*)",
    redirect:"/404/"
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes
});

router.beforeEach((to,from,next) => {
  if(to.meta.requestAuth && !store.state.is_login) {
    next({name: "user_account_login"});
  } else {
    next();
  }
}) 
export default router

4.2实现注册界面

4.3登录状态的持久化,即将token存放在本地

原本token是存在与stroe当中的,即存在于内存当中,这样一刷新token信息就会消失。

登陆时将用户信息存到localStorage当中,退出时将localStorage清空。

因为没有登录的话会被强制跳转到登录界面,所以只需要在登陆见面获取localStorage的信息,如果获取到信息,说明有用户登录,更新vuex当中store中的user相关信息,并且跳转到主页面.

小优化:每次刷新都会有一瞬间的白色见面。这是因为先到的登录界面,然后又到了主界面。瞬间的刷新导致的。所以我们可以事先进行判断是否要到登录界面(有token不用到,其余情况到)

  • 33
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于这个问题,需要先了解什么是Spring Boot、MyBatis-Plus、Ajax、Layui和MySQL,然后再利用它们实现员工的登录注册功能。 1. Spring Boot是一个开源的Java框架,用于构建Spring应用程序。它采用“约定优于配置”的原则,能够快速地构建可独立运行的、生产级别的Spring应用程序。 2. MyBatis-Plus是MyBatis的一个增强工具包。它提供了很多便利的功能,例如代码生成器、分页插件、性能分析插件等,可以减轻开发人员的工作量。 3. Ajax是一种Web开发技术,可以用于在不重新加载整个页面的情况下更新部分页面。它能够提高用户体验,并减少服务器端的负载。 4. Layui是一款轻量级的前端UI框架,提供了丰富的组件和模板,可以快速地构建美观、实用的Web界面。 5. MySQL是一种关系型数据库管理系统,它采用了SQL语言来操作数据库。它是最流行的开源数据库之一,被广泛应用于Web开发领域。 基于以上技术,可以实现员工的登录注册功能。 1. 使用Spring Boot和MyBatis-Plus构建后端API。在员工表中添加用户名和密码字段,编写登录和注册接口,使用MyBatis-Plus的代码生成器来生成数据库访问层的代码。 2. 使用Ajax来实现前端与后端的异步通信。当员工填写了用户名和密码后,通过Ajax提交给后端后端进行验证并返回结果。如果验证通过,前端跳转到主页面,否则给出相应的错误提示。 3. 使用Layui来实现前端页面的美化。根据设计图,使用Layui的组件和模板来构建登录和注册页面。为了提高用户体验,可以使用Layui的表单验证组件来对输入内容进行验证。 4. 使用MySQL来存储员工的信息。在员工表中添加用户名和密码字段,使用MySQL提供的用户名和密码验证功能来进行验证。 最后,需要将后端API部署到服务器上,并将前端页面部署到Web服务器上,使用户可以访问到。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值