• 关于前后端分离


    目录

    1.完成登录

    1.1 完整登录界面

    1.2 登录按钮事件

    1.3 完成登录接口(后端)

    1.3.1 依赖

    1.3.2 mp的代码生成器

    1.3.3 配置application文件

    1.3.4 接口

    1.3.5 跨域问题

    1.3.6 登录成功后前端路由跳转

    2.登录的bug

    2.1 修改登录的接口

    2.2  修改前端登录方法

    3.前置路由守卫

    4.整合shiro

    4.1 依赖

    4.2 shiro的配置类

    4.3 增加一个realm类对象

    4.4 修改controller代码

    4.5  测试登录

    4.6 被shiro的拦截器拦截

    5. 主页的布局

    6. 退出

    6.1 前端

    6.2 后端

    7.查询左侧菜单

    7.1 前端

    7.2 后端


    1.完成登录

    1.1 完整登录界面

    1. <template>
    2. <div id="login_box">
    3. <div class="img_position">
    4. <el-avatar :size="140" :src="imgUrl">el-avatar>
    5. div>
    6. <el-card class="box-card">
    7. <el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
    8. <el-form-item label="用户名" prop="name">
    9. <el-input type="text" v-model="ruleForm.name" autocomplete="off">el-input>
    10. el-form-item>
    11. <el-form-item label="密码" prop="password">
    12. <el-input type="password" v-model="ruleForm.password" autocomplete="off">el-input>
    13. el-form-item>
    14. <el-form-item>
    15. <el-button type="primary"
    16. size="mini"
    17. :plain="true"
    18. @click="login()"
    19. style="margin-left: 100px;width: 100px">登录el-button>
    20. el-form-item>
    21. el-form>
    22. el-card>
    23. div>
    24. template>
    25. <script>
    26. export default {
    27. name: "Login",
    28. data(){
    29. return {
    30. ruleForm: {
    31. name: '',
    32. password: ''
    33. },
    34. rules: {
    35. name: [
    36. {required: true, message:'用户名不能为空', trigger: 'blur'},
    37. ],
    38. password: [
    39. {required: true, message: '密码不能为空', trigger: 'blur'},
    40. ]
    41. },
    42. imgUrl:'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png'
    43. }
    44. },
    45. methods:{
    46. login(){
    47. //表单校验
    48. this.$refs['ruleForm'].validate((valid=>{
    49. if(valid){
    50. //url:后端登录接口的路径
    51. this.$http.post("http://localhost:8808/user/login",this.ruleForm).then(result=>{
    52. if(result.data.code==2000){
    53. //获取登陆成功后的token
    54. var token = result.data.data;
    55. //把token保存在sessionStorage理解为session
    56. sessionStorage.setItem("token",token);
    57. //路由跳转
    58. this.$router.push("/home")
    59. console.log(result.data.msg);
    60. }
    61. })
    62. }
    63. }))
    64. }
    65. }
    66. }
    67. script>
    68. <style>
    69. #login_box{
    70. position: relative;
    71. width: 500px;
    72. margin: 250px auto;
    73. }
    74. #login_box div.img_position{
    75. position: absolute;
    76. left: 200px;
    77. top: -70px;
    78. }
    79. .text {
    80. font-size: 14px;
    81. }
    82. .item {
    83. padding: 18px 0;
    84. }
    85. .box-card {
    86. padding: 100px 50px 0 0;
    87. width: 480px;
    88. }
    89. style>

    1.2 登录按钮事件

    如果想在vue工程中使用axios进行异步请求,则需要在main.js中导入axios
    [1]//导入axios
    import axios from "axios";
    [2]//把axios挂载到vue对象中,以后在vue中如果使用axios直接可以用$http名称
    Vue.prototype.$http=axios

    1. methods:{
    2. login(){
    3. //表单校验
    4. this.$refs['ruleForm'].validate((valid) => {
    5. if(valid){
    6. //url:后端登录接口的路径
    7. this.$http.post("http://localhost:8808/user/login",this.ruleForm).then(result=>{
    8. });
    9. }
    10. })
    11. }
    12. }

     

     1.3 完成登录接口(后端)

    1.3.1 依赖

    1. <dependencies>
    2. <dependency>
    3. <groupId>org.springframework.bootgroupId>
    4. <artifactId>spring-boot-starter-webartifactId>
    5. dependency>
    6. <dependency>
    7. <groupId>org.mybatis.spring.bootgroupId>
    8. <artifactId>mybatis-spring-boot-starterartifactId>
    9. <version>2.2.2version>
    10. dependency>
    11. <dependency>
    12. <groupId>com.fasterxml.jackson.coregroupId>
    13. <artifactId>jackson-databindartifactId>
    14. <version>2.11.3version>
    15. dependency>
    16. <dependency>
    17. <groupId>mysqlgroupId>
    18. <artifactId>mysql-connector-javaartifactId>
    19. <scope>runtimescope>
    20. dependency>
    21. <dependency>
    22. <groupId>org.projectlombokgroupId>
    23. <artifactId>lombokartifactId>
    24. <optional>trueoptional>
    25. dependency>
    26. <dependency>
    27. <groupId>org.springframework.bootgroupId>
    28. <artifactId>spring-boot-starter-testartifactId>
    29. <scope>testscope>
    30. dependency>
    31. <dependency>
    32. <groupId>com.baomidougroupId>
    33. <artifactId>mybatis-plus-generatorartifactId>
    34. <version>3.5.2version>
    35. dependency>
    36. <dependency>
    37. <groupId>org.freemarkergroupId>
    38. <artifactId>freemarkerartifactId>
    39. <version>2.3.31version>
    40. dependency>
    41. <dependency>
    42. <groupId>com.baomidougroupId>
    43. <artifactId>mybatis-plus-boot-starterartifactId>
    44. <version>3.5.2version>
    45. dependency>
    46. <dependency>
    47. <groupId>com.alibabagroupId>
    48. <artifactId>druid-spring-boot-starterartifactId>
    49. <version>1.2.8version>
    50. dependency>
    51. <dependency>
    52. <groupId>com.github.xiaoymingroupId>
    53. <artifactId>swagger-bootstrap-uiartifactId>
    54. <version>1.9.6version>
    55. dependency>
    56. <dependency>
    57. <groupId>com.spring4allgroupId>
    58. <artifactId>swagger-spring-boot-starterartifactId>
    59. <version>1.9.1.RELEASEversion>
    60. dependency>
    61. <dependency>
    62. <groupId>org.springframework.bootgroupId>
    63. <artifactId>spring-boot-starter-data-redisartifactId>
    64. dependency>
    65. <dependency>
    66. <groupId>org.apache.commonsgroupId>
    67. <artifactId>commons-pool2artifactId>
    68. dependency>
    69. <dependency>
    70. <groupId>com.fasterxml.jackson.datatypegroupId>
    71. <artifactId>jackson-datatype-jsr310artifactId>
    72. <version>2.9.3version>
    73. dependency>
    74. <dependency>
    75. <groupId>org.apache.shirogroupId>
    76. <artifactId>shiro-spring-boot-starterartifactId>
    77. <version>1.7.0version>
    78. dependency>
    79. dependencies>

    1.3.2 mp的代码生成器

    1. public class Generator {
    2. public static void main(String[] args) {
    3. FastAutoGenerator.create("jdbc:mysql://localhost:3306/useshiro?serverTimezone=Asia/Shanghai", "root", "root")
    4. .globalConfig(builder -> {
    5. builder.author("孟一") // 设置作者
    6. .enableSwagger() // 开启 swagger 模式
    7. .fileOverride() // 覆盖已生成文件
    8. .outputDir(".\\src\\main\\java\\"); // 指定输出目录
    9. })
    10. .packageConfig(builder -> {
    11. builder.parent("com.wd") // 设置父包名
    12. .moduleName("system") // 设置父包模块名
    13. .pathInfo(Collections.singletonMap(OutputFile.xml, "src\\main\\resources\\mapper\\")); // 设置mapperXml生成路径
    14. })
    15. .strategyConfig(builder -> {
    16. builder.addInclude("acl_user","acl_role","acl_permission")// 设置需要生成的表名
    17. .addTablePrefix("acl_"); // 设置过滤表前缀
    18. })
    19. .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
    20. .execute();
    21. }
    22. }

    1.3.3 配置application文件

    1. server.port=8808
    2. spring.datasource.druid.url=jdbc:mysql://localhost:3306/useshiro?serverTimezone=Asia/Shanghai
    3. spring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driver
    4. spring.datasource.druid.username=root
    5. spring.datasource.druid.password=root
    6. #日志
    7. mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

    1.3.4 接口

    1. @RestController
    2. @RequestMapping("/system")
    3. public class LoginController {
    4. @Autowired
    5. private IUserService userService;
    6. @PostMapping("login")
    7. public CommonResult login(@RequestBody LoginVo loginVo){
    8. QueryWrapper wrapper = new QueryWrapper<>();
    9. wrapper.eq("username",loginVo.getName());
    10. wrapper.eq("password",loginVo.getPassword());
    11. wrapper.eq("isdeleted",0);
    12. User one = userService.getOne(wrapper);
    13. if(one!=null){
    14. return new CommonResult(2000,"登录成功",null);
    15. }else{
    16. return new CommonResult(5000,"登录失败",null);
    17. }
    18. }
    19. }

    1.3.5 跨域问题

    前端调用后端登录接口时出现如下的错误

     为跨域问题:

    当使用异步请求从一个网址访问另一个网址时可能会出现跨域问题。
    前提:
       1. 必须为异步请求
       2. 当端口号或协议或ip不同时则会出现跨域

     出现两个请求: 有一个请求的方式为: OPTIONS 和真实的请求方式

    如何解决跨域:

    1.前端解决
    2.后端解决---->这里也有几种方式:
       【1】可以借助nginx.
       【2】在代码中解决

     在控制层接口上添加@CrossOrigin

     

    (origins = {"192.168.0.111:8080","192.168.0.120:8081"},allowedHeaders="运行哪些请求头跨域",methods={"GET","POST"})

    origins: 允许哪些域可以跨域访问我这个接口
    allowedHeaders:允许哪些请求头信息跨域
    methods: 允许哪些请求方式跨域

    上面再控制层接口处加上注解的方式解决跨,麻烦的地方就需要对每个控制类都加该注解。 设置一个全局跨域配置类。  

    1. @Configuration
    2. public class CorsConfig {
    3. // 当前跨域请求最大有效时长。这里默认1天
    4. private static final long MAX_AGE = 24 * 60 * 60;
    5. @Bean
    6. public CorsFilter corsFilter() {
    7. UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    8. CorsConfiguration corsConfiguration = new CorsConfiguration();
    9. corsConfiguration.addAllowedOrigin("*"); // 1 设置访问源地址
    10. corsConfiguration.addAllowedHeader("*"); // 2 设置访问源请求头
    11. corsConfiguration.addAllowedMethod("*"); // 3 设置访问源请求方法
    12. corsConfiguration.setMaxAge(MAX_AGE);
    13. source.registerCorsConfiguration("/**", corsConfiguration); // 4 对接口配置跨域设置
    14. return new CorsFilter(source);
    15. }
    16. }

    1.3.6 登录成功后前端路由跳转

    2.登录的bug

    上面写的登录,后端没有保存数据 前端也没有拿到数据进行保存

    2.1 修改登录的接口

    1. @RestController
    2. @RequestMapping("/system")
    3. @Api(tags = "登录的接口类")
    4. public class LoginController {
    5. @Autowired
    6. private IUserService userService;
    7. @Autowired
    8. private RedisTemplate redisTemplate;
    9. @PostMapping("login")
    10. @ApiOperation(value="登录接口")
    11. public CommonResult login(@RequestBody LoginVo loginVo){
    12. QueryWrapper wrapper = new QueryWrapper<>();
    13. wrapper.eq("username",loginVo.getName());
    14. wrapper.eq("password",loginVo.getPassword());
    15. wrapper.eq("is_deleted",0);
    16. User one = userService.getOne(wrapper);
    17. if(one!=null){
    18. //随机生成一个唯一字符串。
    19. String token = UUID.randomUUID().toString();
    20. //把该token作为redis的key value为当前登录用户信息
    21. ValueOperations forValue = redisTemplate.opsForValue();
    22. forValue.set(token,one,24, TimeUnit.HOURS);
    23. return new CommonResult(2000,"登录成功",token);
    24. }else{
    25. return new CommonResult(5000,"登录失败",null);
    26. }
    27. }
    28. }

    2.2  修改前端登录方法

     后面每次请求都可以携带该token

     每次请求都得要人为添加参数token. 我们可以使用axios得请求拦截器

     3.前置路由守卫

    前置路由守卫:就是在路由跳转前加上自己得一些业务代码。

    1. //设置前置路由守卫 to:到哪个路由 from:从哪个路由来 next():放行到指定路由
    2. router.beforeEach((to,from,next)=>{
    3. //获取跳转得路径
    4. var path = to.path;
    5. //判断是否为登录路由路径
    6. if(path==="/login"){
    7. console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
    8. //放行
    9. return next();
    10. }
    11. //其他路由路径 判断是否登录过
    12. var token = sessionStorage.getItem("token");
    13. if(token){
    14. return next();
    15. }
    16. //跳转登录
    17. return next("/login");
    18. })

    4.整合shiro

    4.1 依赖

      
                org.apache.shiro
                shiro-spring-boot-starter
                1.7.0
           

    4.2 shiro的配置类

    1. @Configuration
    2. public class ShiroConfig {
    3. @Bean
    4. public DefaultWebSecurityManager securityManager(){
    5. DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
    6. securityManager.setRealm(realm());
    7. return securityManager;
    8. }
    9. @Bean
    10. public Realm realm(){
    11. MyRealm myRealm=new MyRealm();
    12. myRealm.setCredentialsMatcher(credentialsMatcher());
    13. return myRealm;
    14. }
    15. @Bean
    16. public CredentialsMatcher credentialsMatcher(){
    17. HashedCredentialsMatcher credentialsMatcher=new HashedCredentialsMatcher();
    18. credentialsMatcher.setHashAlgorithmName("MD5");
    19. credentialsMatcher.setHashIterations(1024);
    20. return credentialsMatcher;
    21. }
    22. @Resource
    23. private RedisTemplate redisTemplate;//此处之所以引入是因为LoginFilter中的构造函数使用
    24. @Bean(value = "shiroFilter")
    25. public ShiroFilterFactoryBean filterFactoryBean(){
    26. ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
    27. factoryBean.setSecurityManager(securityManager());
    28. //设置拦截规则
    29. HashMap map=new HashMap<>();
    30. map.put("/user/login","anon");
    31. //配置swigger过滤权限
    32. map.put("/**/*.css","anon");
    33. map.put("/**/*.js","anon");
    34. map.put("/doc.html","anon");
    35. map.put("/swagger-resources","anon");
    36. map.put("/v2/api-docs","anon");
    37. map.put("/**","authc");
    38. factoryBean.setFilterChainDefinitionMap(map);
    39. //设置自定义认证过滤器
    40. HashMap filterMap=new HashMap();
    41. filterMap.put("authc",new LoginFilter(redisTemplate));
    42. factoryBean.setFilters(filterMap);
    43. return factoryBean;
    44. }
    45. @Bean //注册filter
    46. public FilterRegistrationBean filterRegistrationBean(){
    47. FilterRegistrationBean filterRegistrationBean=new FilterRegistrationBean<>();
    48. filterRegistrationBean.setName("shiroFilter");
    49. filterRegistrationBean.setFilter(new DelegatingFilterProxy());
    50. filterRegistrationBean.addUrlPatterns("/*");
    51. return filterRegistrationBean;
    52. }
    53. //开始shiro注解
    54. @Bean
    55. public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
    56. AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
    57. authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
    58. return authorizationAttributeSourceAdvisor;
    59. }
    60. @Bean
    61. public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
    62. DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();
    63. advisorAutoProxyCreator.setProxyTargetClass(true);
    64. return advisorAutoProxyCreator;
    65. }
    66. }

    4.3 增加一个realm类对象

    1. public class MyRealm extends AuthorizingRealm {
    2. @Autowired
    3. private IUserService userService;
    4. //授权
    5. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    6. return null;
    7. }
    8. //认证
    9. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    10. String user = (String) authenticationToken.getPrincipal();
    11. QueryWrapper queryWrapper=new QueryWrapper();
    12. queryWrapper.eq("username",user);
    13. queryWrapper.eq("is_deleted",0);
    14. User one = userService.getOne(queryWrapper);
    15. if(one!=null){
    16. ByteSource source = ByteSource.Util.bytes(one.getSalt());
    17. SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(one,one.getPassword(),source,this.getName());
    18. return info;
    19. }
    20. return null;
    21. }
    22. }

    4.4 修改controller代码

    4.5  测试登录

    登录成功后获取用户信息时出现如下得错误

    4.6 被shiro的拦截器拦截

    1. //如果类没有交于spring容器来管理 那么该类中得属性也不能让spring帮你注入
    2. public class LoginFilter extends FormAuthenticationFilter {
    3. //当没有登录时会经过该方法。如果想让他返回json数据那么必须重写该方法
    4. private RedisTemplate redisTemplate; //LoginFilter必须交于spring容器来管理。
    5. // //使用注解注入时,结果为null 重新new一个结果也是空
    6. public LoginFilter(RedisTemplate redisTemplate){
    7. this.redisTemplate=redisTemplate;
    8. }
    9. //当登录成功后执行得方法,如果该方法返回false,则执行onAccessDenied
    10. @Override
    11. protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
    12. HttpServletRequest req= (HttpServletRequest) request;
    13. //1.请求方式是否为OPTIONS
    14. String method = req.getMethod();
    15. if(method!=null&&method.equals("OPTIONS")){
    16. return true;
    17. }
    18. //2.判断请求头是否有token值
    19. String token = req.getHeader("token");
    20. if(token!=null && redisTemplate.hasKey(token)){//只判断token!=null,只能防君子不能防小人 可以伪造token
    21. return true;
    22. }
    23. return false;
    24. }
    25. //未登录时调用该方法 为什么进入没有登录方法
    26. //第一个请求是OPTIONS,没有携带token 第二个因为前端和后端不是用同一个session,默认shiro以sessionid为是否登录的标准
    27. @Override
    28. protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
    29. response.setContentType("application/json;charset=utf-8");
    30. PrintWriter writer=response.getWriter();
    31. CommonResult commonResult=new CommonResult(4001,"未登录",null);
    32. ObjectMapper objectMapper=new ObjectMapper();
    33. String json = objectMapper.writeValueAsString(commonResult);
    34. writer.print(json);//响应给客户json数据
    35. writer.flush();
    36. writer.close();
    37. return false;
    38. }
    39. }

    5. 主页的布局