Spring Boot整合Spring Security(简单数据库连接)

1.引入pom依赖

		<!--这里可以不指定Spring Security的版本号,它会根据SpringBoot的版本来匹配对应的版本-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		
		<!-- 其他工具依赖 -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>1.2.4</version>
		</dependency>
		<dependency>
			<groupId>cn.hutool</groupId>
			<artifactId>hutool-all</artifactId>
			<version>5.4.3</version>
		</dependency>

2.实现自定义配置,首先要创建一个继承于WebSecurityConfigurerAdapter的配置类。

(1)实现自定义拦截配置,首先得告诉Spring Security,用户信息从哪里获取,以及用户对应的角色等信息。这里就需要重写WebSecurityConfigurerAdapter的configure (AuthenticationManagerBuilder auth)方法了。
(2)这个方法将指使Spring Security去找到用户列表,然后再与想要通过拦截器的用户进行比对。

import com.example.springboot.filter.CustomAuthenticationFilter;
import com.example.springboot.handler.FailureHandler;
import com.example.springboot.handler.OutSuccessHandler;
import com.example.springboot.handler.PermissionHandler;
import com.example.springboot.handler.SuccessHandler;
import com.example.springboot.service.impl.UserLoginServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * security config.
 *
 * @author lxp
 * @date 2020 -10-19 17:11:48
 */
@Configuration
//@EnableWebSecurity注解是Spring Security用于启用web安全的注解
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
     

    @Autowired
    private SuccessHandler successHandler;

    @Autowired
    private FailureHandler failureHandler;

    @Autowired
    private OutSuccessHandler outSuccessHandler;

    @Autowired
    private PermissionHandler permissionHandler;

    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
     
        return super.authenticationManager();
    }

    /**
     * 注册自定义过滤器
     * @return
     * @throws Exception the exception
     * @author lxp
     * @date 2020 -10-23 10:39:28
     */
    @Bean
    CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
     
        CustomAuthenticationFilter filter = new CustomAuthenticationFilter();
        filter.setAuthenticationSuccessHandler(successHandler);
        filter.setAuthenticationFailureHandler(failureHandler);
        //过滤器拦截的url要和登录的url一致,否则不生效
        filter.setFilterProcessesUrl("/login");

        //重用WebSecurityConfigurerAdapter配置的AuthenticationManager,不加要自己组装AuthenticationManager
        filter.setAuthenticationManager(authenticationManagerBean());
        return filter;
    }

    /**
     * 密码加密方式
     */
    @Bean
    public BCryptPasswordEncoder passwordEncoder(){
     
        return new BCryptPasswordEncoder();
    }

    /**
     * 自定义UserDetailsService,从数据库中读取用户信息
     * @return
     */
    @Bean
    public UserLoginServiceImpl userLoginService(){
     
        return new UserLoginServiceImpl();
    }

    /**
     * 关联用户认证
     * @param auth
     * @throws Exception the exception
     * @author lxp
     * @date 2020 -10-20 10:45:59
     */
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
     
    //告诉Spring Security你的UserDetailsService实现类是哪个就可以了,它会去调用loadUserByUsername()来查找用户
        auth
                .userDetailsService(userLoginService())
                .passwordEncoder(passwordEncoder());

    }

	//Spring Security的请求拦截配置方法是用户存储配置方法的重载方法
    @Override
    protected void configure(HttpSecurity http) throws Exception {
     
        http.authorizeRequests()
                // 所有用户均可访问的资源(开放的)
                .antMatchers( "/favicon.ico","/css/**","/common/**","/js/**","/images/**","/druid/**","/login","/logout").permitAll()
                // hasAnyRole鉴权(对接口进行User或Admin鉴权)
                .antMatchers( "/test/**","/user/**").hasAnyRole("USER","ADMIN")
                // hasRole对admin下的接口 需要ADMIN权限
                .antMatchers("/admin/**").hasRole("ADMIN")
                // 任何尚未匹配的URL只需要验证用户即可访问
                .anyRequest().authenticated()
                //对于没有配置权限的其他请求允许匿名访问
                //.and().anonymous()
                // 配置登录功能,失败跳转error
                .and()
                //自定义权限(用于json登录)
                .addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)

                .formLogin()
                .loginProcessingUrl("/login")
                // 登陆成功后的处理动作
                .successHandler(successHandler)
                // 登陆失败后的处理动作
                .failureHandler(failureHandler)

                // 配置退出登录页面
                .and().logout()
                .logoutUrl("/logout")
                .logoutSuccessHandler(outSuccessHandler)

                // 权限拒绝的页面
                .and().exceptionHandling().accessDeniedHandler(permissionHandler)
                .and().cors()

                // 禁用 csrf,防止跨站(必须要禁用csrf才能使用ajax登录方式)
                .and().csrf().disable();
    }

}

Spring Security的用户存储配置有多个方案:
(1)内存用户存储
(2)数据库用户存储
(3)LDAP用户存储
(4)自定义用户存储

下面只介绍自定义用户存储:
自定义用户存储,就是自行使用认证名称来查找对应的用户数据,然后交给Spring Security使用。我们需要定义一个实现UserDetailsService的service类:

import com.example.springboot.entity.SysUser;
import com.example.springboot.mapper.SysUserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * User login service.
 *
 * @author lxp
 * @date 2020 -10-20 10:43:53
 */
@Service
public class UserLoginServiceImpl implements UserDetailsService {
     

    @Autowired
    private SysUserMapper sysUserMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws AuthenticationException {
     
        SysUser query = new SysUser();
        query.setUsername(username);
        SysUser user = sysUserMapper.selectOne(query);

        //权限集合
        Collection<GrantedAuthority> collection = new ArrayList<>();

        if (user == null) {
     
            throw new BadCredentialsException("用户" + username +"不存在!");
        }

        // hasRole方法拼接ROLE_
        List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
        GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_"+user.getUsername().toUpperCase());
        grantedAuthorities.add(grantedAuthority);

        // 把获得的账号密码传给security的User
        //注意自定义配置类对密码什么加密方式,这里就调用什么加密方式。
        return new User(user.getUsername(),new BCryptPasswordEncoder().encode(user.getPassword()),grantedAuthorities);
    }
}

说明:
该类只需要实现一个方法:loadUserByUsername()。该方法需要做的是使用传过来的username来匹配一个带有密码等信息的用户实体。需要注意的是这里的User类需要实现UserDetails,也就是说,查到的信息里,必须得有Spring Security所需要的信息。

4.自定义配置类SpringSecurityConfig 请求拦截说明:
Spring Boot整合Spring Security(简单数据库连接)_第1张图片
Spring Boot整合Spring Security(简单数据库连接)_第2张图片
5.如果需要json登录需要配置过滤器:

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;

/**
 * 自定义过滤器
 *
 * @author lxp
 * @date 2020 -10-23 10:17:58
 */
@Slf4j
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
     

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
     
//        if (request.getContentType() == null) {
     
//            throw new AuthenticationServiceException("未登录");
//        }

        if (request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE)
                || request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {
     
            ObjectMapper mapper = new ObjectMapper();
            UsernamePasswordAuthenticationToken authRequest = null;
            try (InputStream is = request.getInputStream()) {
     
                Map<String,String> authenticationBean = mapper.readValue(is, Map.class);
                authRequest = new UsernamePasswordAuthenticationToken(
                        authenticationBean.get("username"), authenticationBean.get("password"));
            } catch (IOException e) {
     
                e.printStackTrace();
                authRequest = new UsernamePasswordAuthenticationToken(
                        "", "");
            } finally {
     
                setDetails(request, authRequest);
                return this.getAuthenticationManager().authenticate(authRequest);
            }
        }
        else {
     
            return super.attemptAuthentication(request, response);
        }
    }
}

6.自定义配置类SpringSecurityConfig中登录成功、失败、权限、退出登录处理:

(1)登录成功处理类:

import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.ContentType;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * The type Success handler.
 *
 * @author lxp
 * @date 2020 -10-22 14:59:38
 */
@Slf4j
@Component
public class SuccessHandler implements AuthenticationSuccessHandler {
     
    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
     

        // authentication 登录成功的对象,默认用户名
        log.info("登录成功,{}", authentication);

        // 跨域处理
        httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
        // 允许的请求方法
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        // 允许的请求头
        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
        // 设置响应头
        httpServletResponse.setContentType("application/json;charset=utf-8");

        authentication = SecurityContextHolder.getContext().getAuthentication();

        // 返回值
        // 拼装数据返回对象
        Dict res = Dict.create().set("code", 0).set("msg", "登录成功").set("data", authentication);

        // 设置一下输出json字符串时的编码
        String contentType = ContentType.JSON.toString(CharsetUtil.CHARSET_UTF_8);

        // 响应json字符串
        ServletUtil.write(httpServletResponse, JSONUtil.toJsonStr(res), contentType);
    }
}

(2)登录失败处理类:

import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.ContentType;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.*;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * The type Failure handler.
 *
 * @author lxp
 * @date 2020 -10-22 14:59:41
 */
@Slf4j
@Component
public class FailureHandler implements AuthenticationFailureHandler {
     
    @Override
    public void onAuthenticationFailure(
            HttpServletRequest request, HttpServletResponse response,
            AuthenticationException exception
    ) throws IOException, ServletException {
     
        log.info("登录失败,{}", exception);
        Dict res = Dict.create().set("code", 1000).set("msg", "登录失败");

        // 根据返回的异常类,确定登录失败的原因
        if (exception instanceof UsernameNotFoundException) {
     
            res.set("data", "用户名不存在");
        } else if (exception instanceof LockedException) {
     
            res.set("data", "账号被锁定");
        } else if (exception instanceof DisabledException) {
     
            res.set("data", "账号被禁用");
        } else if (exception instanceof CredentialsExpiredException) {
     
            res.set("data", "密码过期");
        } else if (exception instanceof AccountExpiredException) {
     
            res.set("data", "账号过期");
        } else if (exception instanceof BadCredentialsException) {
     
            res.set("data", "账号密码输入有误");
        } else {
     
            res.set("data", "登录失败(" + exception.getMessage() + ")");
        }

        // 响应json字符串
        String contentType = ContentType.JSON.toString(CharsetUtil.CHARSET_UTF_8);
        ServletUtil.write(response, JSONUtil.toJsonStr(res), contentType);
    }
}

(3)权限不足处理类:

import com.alibaba.fastjson.JSON;
import com.example.springboot.common.Response;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * Created by Administrator on 2020/10/23.
 */
@Component
public class PermissionHandler implements AccessDeniedHandler {
     
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
     
        // 设置响应头
        httpServletResponse.setContentType("application/json;charset=utf-8");
        // 返回值
        Response result = Response.createBySuccessWithoutData("权限不足,无法登陆");
        httpServletResponse.getWriter().write(JSON.toJSONString(result));
    }
}

(4)退出登录处理类:

import com.alibaba.fastjson.JSON;
import com.example.springboot.common.Response;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * The type Out success handler.
 *
 * @author lxp
 * @date 2020 -10-22 14:59:33
 */
@Component
public class OutSuccessHandler implements LogoutSuccessHandler {
     
    @Override
    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
     
        // 设置响应头
        httpServletResponse.setContentType("application/json;charset=utf-8");
        // 返回值
        Response result = Response.createBySuccessWithoutData("退出登陆成功");
        httpServletResponse.getWriter().write(JSON.toJSONString(result));
    }
}

7.实体类SysUser:

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

import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;

@Data
@Table(name = "sys_user")
public class SysUser implements UserDetails,Serializable {
     
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private String id;

    private String username;

    private String password;

    private List<? extends GrantedAuthority> authorities;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
     
        return authorities;
    }

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

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

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

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

实体类User:

import java.io.Serializable;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;

import javax.persistence.Column;
import javax.persistence.Id;
import javax.persistence.Table;

/**
 * The type User.
 *
 * @author lxp
 * @date 2020 -10-19 15:10:03
 */
@Data
@Table(name = "user")
@JsonIgnoreProperties(ignoreUnknown = true)
public class User implements Serializable {
     

    private static final long serialVersionUID = 1222221L;

    @Id
    @Column(name = "id")
    private Integer id;

    @Column(name = "user_name")
    private String userName;

    @Column(name = "pass_word")
    private String passWord;

    @Column(name = "real_name")
    private String realName;
}

8.其他类:接口、实现类、mapper在上一篇博客。

9.编写测试类:

import com.example.springboot.common.Response;
import com.example.springboot.entity.User;
import com.example.springboot.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * 测试类模块
 *
 * @author lxp
 * @date 2020 -10-19 14:50:51
 */
@RestController
public class TestController {
     

    @Value("${server.port}")
    String port;

    @Autowired
    private UserService userService;

    @GetMapping(value = "/test")
    public String test(@RequestParam(required = false) Integer id){
     
        User user = userService.getUserInfo(id);
        StringBuilder stringBuilder = new StringBuilder("欢迎进入"+port+"号客户端!"+"
"
+"人员名称是:"+user.getUserName()); return stringBuilder.toString(); } @PostMapping(value = "/user/testUser") public String testUser(){ StringBuilder stringBuilder = new StringBuilder("欢迎==员工==登录!"); return stringBuilder.toString(); } @PostMapping(value = "/admin/testAdmin") public String testAdmin(){ StringBuilder stringBuilder = new StringBuilder("欢迎==管理员==登录!"); return stringBuilder.toString(); } }

管理员登录:
Spring Boot整合Spring Security(简单数据库连接)_第3张图片
管理员权限接口成功:
Spring Boot整合Spring Security(简单数据库连接)_第4张图片
用户登录成功:
Spring Boot整合Spring Security(简单数据库连接)_第5张图片
用户登录管理员权限接口:
Spring Boot整合Spring Security(简单数据库连接)_第6张图片

你可能感兴趣的:(SpringBoot,Spring全家桶,Spring,spring,boot,spring,security)