SpringBoot整合Security入门教程

SpringBoot整合Security入门教程

前言

严格来说,这并不算什么教程,只是记录我刚开始接触SpringSecurity所遇到的坑,最后成功实现的过程。网上的教程五花八门,尝试了好多都没运行成功,最后参考了这篇 springboot 集成 spring security 详细 附代码最终才成功,所以这里记录下自己的步骤,希望可以帮助和我一样的小白。

一、所需文件概述

在这里插入图片描述
下面来稍微介绍一下:
a、controller层和login.html页面不必多说

b、UserInfo实现了UserDetails

c、SecurityConfig配置了登录授权和身份认证等信息

d、UserDetailsServiceImpl实现了UserDetailsService类

e、MyAuthenticationProvider实现了AuthenticationProvider类

二、详细介绍内容
1.UserInfo.class
package com.braisedpanda.web.security;

import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;

import java.io.Serializable;
import java.util.Collection;

/**
* @program: admin-dashboard
* @description:
* @create: 2019-11-21 09:08
**/
@Data
public class UserInfo implements UserDetails,Serializable {
/*这些属性是自己可以定义的,可以不用定义太多,仅仅用来临时作为验证用的,一般不会存入数据库*/
  private static final long serialVersionUID = 1L;
  private String username;  //用户名
  private String password;  //密码
  private String role;			//角色
  private Integer status;		//用户状态 禁用|启动
  private boolean accountNonExpired;  //账户是否未过期,过期无法验证
  private boolean accountNonLocked;  //指定用户是否解锁,锁定的用户无法进行身份验证
  private boolean credentialsNonExpired;  //指示是否已过期的用户的凭据(密码),过期的凭据防止认证
  private boolean enabled;  //是否可用 ,禁用的用户不能身份验证

  public UserInfo(String username, String password, String role, boolean accountNonExpired, boolean accountNonLocked, boolean credentialsNonExpired, boolean enabled) {
      this.username = username;
      this.password = password;
      this.role = role;
      this.accountNonExpired = accountNonExpired;
      this.accountNonLocked = accountNonLocked;
      this.credentialsNonExpired = credentialsNonExpired;
      this.enabled = enabled;
  }
/*下面的override都是必须实现的方法*/
  @Override
  public Collection<? extends GrantedAuthority> getAuthorities() {
      return  AuthorityUtils.commaSeparatedStringToAuthorityList(role);
  }

  @Override
  public String getPassword() {
      return password;
  }

  @Override
  public String getUsername() {
      return username;
  }

  @Override
  public boolean isAccountNonExpired() {
      return accountNonExpired;
  }

  @Override
  public boolean isAccountNonLocked() {
      return accountNonLocked;
  }

  @Override
  public boolean isCredentialsNonExpired() {
      return credentialsNonExpired;
  }

  @Override
  public boolean isEnabled() {
      return enabled;
  }
}

UserInfo: 上面的注释也说了,这个类是用作security验证用的,虽然和我们常用自己定义的User类很相似,但是UserInfo一般不存入数据库,我们仍然需要自己定义一个User类作为数据库存储,之后验证用户登录的时候,会从这个user表中查用户

2.SecurityConfig.class
package com.braisedpanda.web.security;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.web.filter.CharacterEncodingFilter;

import javax.annotation.Resource;
import javax.sql.DataSource;

/**
 * @program: admin-dashboard
 * @description: 配置拦截和验证
 * @create: 2019-11-21 08:46
 **/
@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
    @Resource
    private  DataSource dataSource;

    @Autowired
    private PersistentTokenRepository tokenRepository;

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Autowired  //必须
    private MyAuthenticationProvider provider;  //注入我们自己的AuthenticationProvider

    /**
    * @Description: 授权配置(必须)

    * @Date: 2019/11/21 0021
    */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        /**登录授权*/
        http
                .authorizeRequests().antMatchers("/css/**","/js/**","/fonts/**","/login/**","/sass/**","/fonts/**").permitAll() //静态资源不被拦截
                .and()
                .authorizeRequests().antMatchers("/regist/**","/toregist/**").permitAll()  //用户注册页面不被拦截
                .and()
                .authorizeRequests().anyRequest().authenticated()   //其余页面都需要认证(只有登录后才可以正常访问)
                .and()
                .formLogin().loginPage("/tologin")            //表单登录页面
                .loginProcessingUrl("/login")                 //表单登录方法
                .permitAll()

                .and()
                .logout()
                .logoutUrl("/logout")                       //退出登录,退出登录时,会清楚remember-me中响应的登录token
                .and()
                .rememberMe()
                .tokenRepository(tokenRepository)           // 设置数据访问层,勾选remember-me时,会存入token在数据库
                .rememberMeServices(rememberMeServices())   // 读取数据库中remember-me的相关token
                .key("INTERNAL_SECRET_KEY")
                .tokenValiditySeconds(60*30) // 记住我的时间(秒)
                .and()
                .csrf().disable();

        /**session失效管理*/
        http.sessionManagement().invalidSessionUrl("/tologin");

        /**退出删除cookie*/
        http.logout().deleteCookies("JESSIONID");

        /**解决中文乱码问题*/
        CharacterEncodingFilter filter = new CharacterEncodingFilter();
        filter.setEncoding("UTF-8");
        filter.setForceEncoding(true);
        http.addFilterBefore(filter,CsrfFilter.class);

    }


    /**
     * @Description: 验证身份类(必须)

     * @Date: 2019/11/21 0021
     */

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception{

        auth.authenticationProvider(provider);

    }

    /**生成remember-me token存入数据库**/
    @Bean
    public PersistentTokenRepository tokenRepository() {
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(dataSource);

        return tokenRepository;
    }
    /** remember-me 在指定的时间内可以免登陆**/
    @Bean
    public RememberMeServices rememberMeServices() {
        JdbcTokenRepositoryImpl rememberMeTokenRepository = new JdbcTokenRepositoryImpl();
        // 此处需要设置数据源,否则无法从数据库查询验证信息
        rememberMeTokenRepository.setDataSource(dataSource);

        // 此处的 key 可以为任意非空值(null 或 ""),但必须和前面保持一致

        PersistentTokenBasedRememberMeServices rememberMeServices =
                new PersistentTokenBasedRememberMeServices("INTERNAL_SECRET_KEY", userDetailsService, rememberMeTokenRepository);

        return rememberMeServices;
    }

}

SecurityConfig.class: 注释说的挺清楚了,刚入门可以不用配置这么复杂,保留login的相关配置就可以了,remember-me的配置都可以删掉

3.UserDetailsServiceImpl.class
package com.braisedpanda.web.security;


import com.braisedpanda.commons.model.User;

import com.braisedpanda.web.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
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.Component;

/**
 * @program: admin-dashboard
 * @description:  通过用户名在数据库中查找角色信息

 * @create: 2019-11-21 09:12
 **/
@Component
public class UserDetailsServiceImpl implements UserDetailsService{
    @Autowired
    private UserService userService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userService.getUserByUsername(username);
        if(user != null){
            String name = user.getUsername();
            String password = user.getPassword();

            UserInfo userInfo=new UserInfo(name, password, "ROLE_ADMIN", true,true,true, true);
            userInfo.setStatus(user.getStatus());
            return userInfo;
        }else
            return null;
    }
}

UserDetailsServiceImpl.class: 这个功能一目了然,就是根据表单传入的username来查找数据库中响应的user,值得注意的是,重写的这个方法返回类型是UserDetails,上面我们定义的UserInfo就是实现了UserDetails,所以返回UserInfo, userInfo中存入了通过数据库表中根据username所查到的用户的用户名和密码,当然还可以查权限相关的信息,这里没有涉及到权限,就回传了ROLE_ADMIN,没啥作用,不要被这个忽悠了。

4.MyAuthenticationProvider.class
package com.braisedpanda.web.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;

import java.util.Collection;

/**
 * @program: admin-dashboard
 * @description: 验证用户名和密码是否正确,如果错误,抛出错误信息

 * @create: 2019-11-21 09:16
 **/
@Component
public class MyAuthenticationProvider implements AuthenticationProvider {

    /**
     * 注入我们自己定义的用户信息获取对象
     */
    @Autowired
    private UserDetailsServiceImpl userDetailService;
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {


        String userName = authentication.getName();// 这个获取表单输入中返回的用户名;
        String password = (String) authentication.getCredentials();// 这个是表单中输入的密码;
        // 这里构建来判断用户是否存在和密码是否正确
        UserInfo userInfo = (UserInfo) userDetailService.loadUserByUsername(userName); // 这里调用我们的自己写的获取用户的方法;

        if (userInfo == null) {
            throw new BadCredentialsException("用户名不存在");
        }

        if (!userInfo.getPassword().equals(password)) {
            throw new BadCredentialsException("密码不正确");
        }
        //这个判断状态可加可不加的,也可以在userinfo定义其他的条件,在这里判断,比如定义过期时间什么的,随便定义
        if(userInfo.getStatus() == 0 ){
            throw new BadCredentialsException("用户状态被禁用");
        }
        Collection<? extends GrantedAuthority> authorities = userInfo.getAuthorities();
        // 构建返回的用户登录成功的token
        return new UsernamePasswordAuthenticationToken(userInfo, password, authorities);
    }
    
    @Override
    public boolean supports(Class<?> authentication) {
        return true;

    }
}

MyAuthenticationProvider.class:
这个类使用了UserDetailsServiceImpl 传过来的userinfo,根据userinfo中的信息来比对表单传过来的username和password,当然也可以判断其他条件,比如:用户状态,用户是否过期… 这些条件只需在userinfo中自己灵活定义即可。抛出的异常信息 throw new BadCredentialsException(“xxxxx”) 可以在前端页面进行显示

5.controller(不必多说,就是最简单的跳转)
package com.braisedpanda.web.controller;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;

/**
 * @program: admin-dashboard
 * @description:

 * @create: 2019-11-20 10:31
 **/
@RestController
public class LoginController {

    /**
    * @Description: 跳转到登录界面

    * @Date: 2019/11/20 0020
    */
    @RequestMapping("/tologin")
    public ModelAndView tologin(){
        return new ModelAndView("login");
    }

    /**
    * @Description: 用户登录

    * @Date: 2019/11/20 0020
    */
    @RequestMapping("/login")
    public ModelAndView login(){

        return new  ModelAndView("index");
    }

    /**
     * @Description: 退出登录

     * @Date: 2019/11/20 0020
     */
    @RequestMapping("/logout")
    public ModelAndView logout(){

        return new  ModelAndView("login");
    }

    @RequestMapping("/regist")
    public ModelAndView regist(){

        return new  ModelAndView("login");
    }

    @RequestMapping("/toregist")
    public ModelAndView toregist(){

        return new  ModelAndView("regist");
    }
    
}

6.login.html(最简单的登录表格就可以了,这里贴下我的提供参考)
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
	<title>登录</title>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1">
<!--===============================================================================================-->	
	<link rel="icon" type="image/png" href="/login/images/icons/favicon.ico"/>
<!--===============================================================================================-->
	<link rel="stylesheet" type="text/css" href="/login/vendor/bootstrap/css/bootstrap.min.css">
<!--===============================================================================================-->
	<link rel="stylesheet" type="text/css" href="/login/fonts/font-awesome-4.7.0/css/font-awesome.min.css">
<!--===============================================================================================-->
	<link rel="stylesheet" type="text/css" href="/login/fonts/Linearicons-Free-v1.0.0/icon-font.min.css">
<!--===============================================================================================-->
	<link rel="stylesheet" type="text/css" href="/login/vendor/animate/animate.css">
<!--===============================================================================================-->	
	<link rel="stylesheet" type="text/css" href="/login/vendor/css-hamburgers/hamburgers.min.css">
<!--===============================================================================================-->
	<link rel="stylesheet" type="text/css" href="/login/vendor/animsition/css/animsition.min.css">
<!--===============================================================================================-->
	<link rel="stylesheet" type="text/css" href="/login/vendor/select2/select2.min.css">
<!--===============================================================================================-->	
	<link rel="stylesheet" type="text/css" href="/login/vendor/daterangepicker/daterangepicker.css">
<!--===============================================================================================-->
	<link rel="stylesheet" type="text/css" href="/login/css/util.css">
	<link rel="stylesheet" type="text/css" href="/login/css/main.css">
<!--===============================================================================================-->
</head>
<body style="background-color: #666666;">
	
	<div class="limiter">
		<div class="container-login100">
			<div class="wrap-login100">
				<form class="login100-form validate-form" th:action="@{/login}" method="post">
					<span class="login100-form-title p-b-43">
						登录
					</span>

					<div th:if="${param.error}" th:text="${session?.SPRING_SECURITY_LAST_EXCEPTION?.message}"></div>
					<div class="wrap-input100 validate-input" data-validate = "用户名不能为空">
						<input class="input100"  name="username">
						<span class="focus-input100"></span>
						<span class="label-input100">用户名</span>
					</div>
					
					
					<div class="wrap-input100 validate-input" data-validate="密码不能为空">
						<input class="input100" type="password" name="password">
						<span class="focus-input100"></span>
						<span class="label-input100">密码</span>
					</div>

					<div class="flex-sb-m w-full p-t-3 p-b-32">
						<div class="contact100-form-checkbox">
							<input class="input-checkbox100" id="ckb1" type="checkbox" name="remember-me">
							<label class="label-checkbox100" for="ckb1">
								记住我
							</label>
						</div>

						<div>
							<a href="#" class="txt1">
								忘记密码?
							</a>
						</div>
					</div>
			

					<div class="container-login100-form-btn">
						<input class="login100-form-btn" type="submit" value="登录">

						</input>
					</div>
					

				</form>

				<div class="login100-more" style="background-image: url('/login/images/bg-011.jpg');">
				</div>
			</div>
		</div>
	</div>
	
	

	
	
<!--===============================================================================================-->
	<script src="/login/vendor/jquery/jquery-3.2.1.min.js"></script>
<!--===============================================================================================-->
	<script src="/login/vendor/animsition/js/animsition.min.js"></script>
<!--===============================================================================================-->
	<script src="/login/vendor/bootstrap/js/popper.js"></script>
	<script src="/login/vendor/bootstrap/js/bootstrap.min.js"></script>
<!--===============================================================================================-->
	<script src="/login/vendor/select2/select2.min.js"></script>
<!--===============================================================================================-->
	<script src="/login/vendor/daterangepicker/moment.min.js"></script>
	<script src="/login/vendor/daterangepicker/daterangepicker.js"></script>
<!--===============================================================================================-->
	<script src="/login/vendor/countdowntime/countdowntime.js"></script>
<!--===============================================================================================-->
	<script src="/login/js/main.js"></script>

</body>
</html>
三、效果演示

登录界面
在这里插入图片描述
登录失败
在这里插入图片描述

好了,基本的入门核心内容就是这么多了,最后附上我的代码,仅供参考代码地址
希望能帮助到和我一样第一次第一次接触security的小白们

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值