Java

spring boot +spring security 前后端分离项目

内容纲要

因为毕设设计项目需要,前后端分离和权限管理是必要的。

先占坑,功能已经实现,有时间再来填坑

更新,填坑,

第一部分,Configuration配置

SecurityConfig 类是继承了WebSecurityConfigurerAdapter类,完成自定义登录验证方式。在这里我使用了sping session redis进行对session的缓存,并实现单点登录.


@Configuration
@EnableRedisHttpSession
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    //自定义提供用户和密码登录类
    @Autowired
    private werls.scis.service.UserServiceImpl userService;
	//自定义密码验证类
    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private AuthenticationProvider authenticationProvider;
	
    @Autowired
    AppAuthenticationSuccessHandler appAuthenticationSuccessHandler;

    @Autowired
    private AppAuthenticationFailureHandler appFailureHandler;

    @Autowired
    AppAccessDeniedHandler appAccessDeniedHandler;

    @Autowired
    private AppExpiredSessionStrategy appExpiredSessionStrategy;

    @Autowired
    MyLogoutSuccessHandler logoutSuccessHandler;


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

    @Bean
    public AuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setHideUserNotFoundExceptions(false);
        provider.setUserDetailsService(userService);
        provider.setPasswordEncoder(passwordEncoder);
        return provider;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authenticationProvider);
    }

    /**
     * 设置了登录页面,而且登录页面任何人都可以访问,然后设置了登录失败地址,也设置了注销请求,注销请求也是任何人都可以访问的。
     * permitAll表示该请求任何人都可以访问,.anyRequest().authenticated(),表示其他的请求都必须要有权限认证。
     * */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        System.out.println("AppSecurityConfigurer configure http......");
        System.out.println(new BCryptPasswordEncoder().encode("123456"));
        http.authorizeRequests()
                .antMatchers("/login","/register","/public/**","/home/**").permitAll()
                .antMatchers("/student/**","/i/**").hasAnyRole("STU","ADMIN", "TEA")
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login")
                .usernameParameter("username").passwordParameter("password")
                .successHandler(appAuthenticationSuccessHandler)
                .failureHandler(appFailureHandler)
                .permitAll()
                .and()
                .exceptionHandling()
                //没有权限,返回json
                .accessDeniedHandler(appAccessDeniedHandler)
                .and()
                .logout()
                .logoutSuccessHandler(logoutSuccessHandler)
                .permitAll()
                .and()
                .cors().disable()
                .csrf().disable()/*部署时需要关掉*/
                .exceptionHandling()
                .and()
                .sessionManagement()
                .invalidSessionUrl("/login/invalid")
                .maximumSessions(1)
                .maxSessionsPreventsLogin(false)
                //当达到最大值时,旧用户被踢出后的操作
                .expiredSessionStrategy(appExpiredSessionStrategy);
    }

}

以上部分就是核心配置类,接下来是登录成功时返回的json类。在这里有能力的大佬可以使用token保持无状态连接,因为在spring security是默认使用session保持会话,想要实现的话需要自己重新验证过滤器。


AuthenticationSuccessHandler 实现类

@Component
public class AppAuthenticationSuccessHandler  implements AuthenticationSuccessHandler {
    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private ObjectMapper objectMapper;


    /**
     * Called when a user has been successfully authenticated.
     *
     * @param request        the request which caused the successful authentication
     * @param response       the response
     * @param authentication the <tt>Authentication</tt> object which was created during
     */
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request,
                                        HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {
        logger.info("登录成功");
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("code", 200);
        map.put("message", "登录成功,正在转跳");
        response.setContentType("application/json;charset=utf-8");
        PrintWriter out = response.getWriter();
        out.write(objectMapper.writeValueAsString(map));
        out.flush();
        out.close();
    }
}

AuthenticationFailureHandler 实现类

@Component
public class AppAuthenticationFailureHandler implements AuthenticationFailureHandler {
    private Logger logger = LoggerFactory.getLogger(getClass());
    @Autowired
    private ObjectMapper objectMapper;
    /**
     * Called when an authentication attempt fails.
     *
     * @param request   the request during which the authentication attempt occurred.
     * @param response  the response.
     * @param exception the exception which was thrown to reject the authentication
     */
    @Override
    public void onAuthenticationFailure(HttpServletRequest request,
                                        HttpServletResponse response,
                                        AuthenticationException exception) throws IOException, ServletException {
        logger.info("登录失败");
        response.setContentType("application/json;charset=utf-8");
        PrintWriter out = response.getWriter();
        Map<String,Object> map = new HashMap<String,Object>();
        map.put("code",401);
        if (exception instanceof UsernameNotFoundException || exception instanceof BadCredentialsException) {
            map.put("message","用户名或密码错误");
        } else if (exception instanceof DisabledException) {
            map.put("message","账户被禁用");
        } else {
            map.put("message","登录失败!");
        }
        out.write(objectMapper.writeValueAsString(map));
        out.flush();
        out.close();
    }
}

SessionInformationExpiredStrategy 实现类

@Component
public class AppExpiredSessionStrategy implements SessionInformationExpiredStrategy {

    private ObjectMapper objectMapper = new ObjectMapper();
    @Override
    public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
        Map<String,Object> map = new HashMap<>();
        map.put("code",0);
        map.put("message","已经在另一台机器登录,您被迫下线。" + event.getSessionInformation().getLastRequest());
        String s = objectMapper.writeValueAsString(map);
        event.getResponse().setContentType("application/json;charset=UTF-8");
        event.getResponse().getWriter().write(s);
    }
}

LogoutSuccessHandler 实现类

/**
 * @author : LiJiWei
 * @version V1.0
 * @Project: scis
 * @Package werls.scis.security
 * @Description: TODO
 * @date Date : 2020年03月01日 22:25
 */
@Component
public class MyLogoutSuccessHandler  implements LogoutSuccessHandler{
    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public void onLogoutSuccess(HttpServletRequest request,
                                HttpServletResponse response,
                                Authentication authentication) throws IOException, ServletException {
        Map<String,Object> map = new HashMap<String,Object>();
        map.put("code",200);
        map.put("message","退出成功");
        map.put("data",authentication);
        response.setContentType("application/json;charset=utf-8");
        PrintWriter out = response.getWriter();
        out.write(objectMapper.writeValueAsString(map));
        out.flush();
        out.close();
    }
}

AccessDeniedHandler 实现类

@Component
public class AppAccessDeniedHandler implements AccessDeniedHandler {

    @Autowired
    private ObjectMapper objectMapper;

    /**
     * Handles an access denied failure.
     *
     * @param request               that resulted in an <code>AccessDeniedException</code>
     * @param response              so that the user agent can be advised of the failure
     * @param accessDeniedException that caused the invocation
     * @throws IOException      in the event of an IOException
     * @throws ServletException in the event of a ServletException
     */
    @Override
    public void handle(HttpServletRequest request,
                       HttpServletResponse response,
                       AccessDeniedException accessDeniedException) throws IOException, ServletException {
        response.setContentType("application/json;charset=utf-8");
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        PrintWriter out = response.getWriter();
        Map<String,Object> map = new HashMap<String,Object>();
        map.put("code",403);
        map.put("message", "权限不足");
        out.write(objectMapper.writeValueAsString(map));
        out.flush();
        out.close();
    }
}

第二部分,前端登录

前端页面部分

我自己使用的vue.js作为前端,这个了给出的是vue下的。

  • 这是HTML部分。
<div class="login-container" >
        <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" auto-complete="on"
                 label-position="left">

            <div class="title-container">
                <h2 class="title">登 录</h2>
            </div>

            <el-form-item prop="username">
        <span class="svg-container">
          <svg-icon icon-class="user"/>
        </span>
                <el-input
                        ref="username"
                        v-model="loginForm.username"
                        placeholder="学号/工号/身份号/手机/邮箱"
                        name="username"
                        type="text"
                        tabindex="1"
                        auto-complete="on"
                />
            </el-form-item>

            <el-form-item prop="password3">
        <span class="svg-container">
          <svg-icon icon-class="password"/>
        </span>
                <el-input
                        :key="passwordType"
                        ref="password"
                        v-model="loginForm.password"
                        :type="passwordType"
                        placeholder="密码"
                        name="password"
                        tabindex="2"
                        auto-complete="on"
                        @keyup.enter.native="handleLogin"
                />
                <span class="show-pwd" @click="showPwd">
          <svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'"/>
        </span>
            </el-form-item>

            <el-button :loading="loading" type="primary" style="width:100%;margin-bottom:30px;"
                       @click.native.prevent="handleLogin">Login
            </el-button>

            <div style="position:relative">
                <div class="tips">
                    <span> <el-link type="primary" @click="toHome">返回首页</el-link></span>

                </div>
                <div class="tips">
                    <span style="margin-right:18px;">
                        <el-link type="primary" >{{msg}}</el-link>
                    </span>
                </div>

                <el-button class="thirdparty-button" type="primary" @click="passwordRecover">
                    忘记密码
                </el-button>

            </div>

        </el-form>
    </div>
  • js部分
  export default {
        name: "Login",
        data() {
            const validateUsername = (rule, value, callback) => {
                if (value.length < 1) {
                    callback(new Error('请输入你的用户名'))
                } else {
                    callback()
                }
            }
            const validatePassword = (rule, value, callback) => {
                if (value.length < 6) {
                    callback(new Error('密码长度不能小于6位'))
                } else {
                    callback()
                }
            }
            return {
                loginForm: {
                    username: 'root',
                    password: '123456'/*默认*/
                },

                loginRules: {
                    username: [{required: true, trigger: 'blur', validator: validateUsername}],
                    password: [{required: true, trigger: 'blur', validator: validatePassword}]
                },
                passwordType: 'password',
                capsTooltip: false,
                loading: false,
                msg:''
            }
        },

        created() {//初始化后

        },
        mounted() {//创建vm.$el之后
            /*判空*/
            if (this.loginForm.username === '') {
                this.$refs.username.focus()
            } else if (this.loginForm.password === '') {
                this.$refs.password.focus()
            }
        },
        destroyed() {//销毁之后

        },
        methods: {
            showPwd() {
                if (this.passwordType === 'password') {
                    this.passwordType = ''
                } else {
                    this.passwordType = 'password'
                }
                this.$nextTick(() => {
                    this.$refs.password.focus()
                })
            },
            handleLogin() {/*登录前检验*/

                this.$refs.loginForm.validate(valid => {
                    if (valid) {
                        this.loading = true
                        this.$store.dispatch('user/login', this.loginForm)
                            .then(response => {

                                if (response.data.code === 401) {
                                    this.$notify.error({
                                        title: '错误',
                                        message: response.data.message
                                    });

                                } else if (response.data.code === 200) {
                                    this.$notify.success({
                                        title: '登录成功',
                                        message: response.data.message
                                    });
                                 this.$router.push({path: '/home' || '/'})
                                }

                                this.loading = false
                            })
                            .catch(error => {
                                this.$message.error("出现了一些问题" + error)
                                this.loading = false
                            })
                    } else {
                        this.$notify.error({
                            title: '错误',
                            message: "错误提交"
                        });
                        return false
                    }

                })
            },
            passwordRecover(){
                this.$router.push({path:'/password/recover'});
                console.log("忘记")
            },
            toHome(){
                console.log("home")
                this.$router.push({path:'/'});
            }
        }
    }
  • store部分
const actions = {
  // 用户登录 异步
  login({commit}, userInfo) {


    return new Promise((resolve, reject) => {

      postFrom("/login", userInfo)
          .then(response => {
            const {data} = response
            commit('SET_TOKEN', data.token)//使用用户名
            setToken(data.token)
            resolve(response)
            // this.getInfo(commit)
          })
          .catch(errors => {
            reject(errors)
          })
    })
  }
  • postFrom部分,这里很重要,因为没有对security的做修改,因此登录是无法解析json格式的,这里可以查看源码
export const postFrom =(url,params)=>{
        return axios ({
            method: 'post',
            url:'/api'+url,
            data:params,
            transformRequest:[function (data) {
                return qs.stringify(data)
            }],
            header: {
                'Content-Type': 'application/x-www-form-urlencoded'
            }
        })
}

到此完成。

马上对线!

Title - Artist
0:00