SpringBoot整合SpringSecurity(一)入门程序

序言

Spring Security是一个功能强大且可高度自定义的身份验证和访问控制框架。它是保护基于Spring的应用程序的事实标准。

Spring Security是一个专注于为Java应用程序提供身份验证和授权的框架。与所有Spring项目一样,Spring Security的真正强大之处在于它可以轻松扩展以满足自定义要求。

这是Spring Security 官方的说明,早就听闻 Spring Security 功能强大但上手困难,于是上手学习了学习,在此整理出几篇文章,百度的坑是真的多。

代码请参考 https://github.com/AutismSuperman/springsecurity-example

首先依赖

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-securityartifactId>
dependency>

准备页面


<html>
<head>
    <meta charset="UTF-8">
    <title>登录title>
head>
<body>
<h3>表单登录h3>
<table>
    <tr>
        <td>用户名:td>
        <td><input type="text" autocomplete="off" name="username">td>
    tr>
    <tr>
        <td>密码:td>
        <td><input type="password" autocomplete="off" name="password">td>
    tr>
    <tr>
        <td colspan="2">
            <button type="button" onclick="login()">登录button>
        td>
    tr>
table>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js">script>
<script>
    function login() {
        var username = $("input[name=username]").val();
        var password = $("input[name=password]").val();
        if (username === "" || password === "") {
            alert("用户名或密码不能为空");
            return;
        }
        $.ajax({
            type: "POST",
            url: "/authentication/form",
            data: {
                "username": username,
                "password": password
            },
            success: function (e) {
                alert("登陆成功")
                setTimeout(function () {
                    location.href = '/hello';
                }, 1500);
            },
            error: function (e,a,b) {
                console.log(e.responseText);
                alert("登陆失败")
            }
        });
    }

script>
body>
html>

<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>hellotitle>
head>
<body>
<h2>hello world from fulinlin.h2>
<a href="/logout">退出登录a>
body>
html>

然后是controller

@Controller
@RequestMapping
public class TestController {


    @RequestMapping("/hello")
    public String hello() {
        return "hello";
    }

    @RequestMapping("/login")
    public String login() {
        return "login";
    }
}

准备测试用户

这里呢我们就不连数据库了,创造一些模拟的数据

首先呢是用户的实体类

@Data
public class SysUser  {

    private Long id;
    private String userName;
    private String password;
    private List<String> roles;
    
    public SysUser() {
    }
    public SysUser(Long id, String userName, String password, List<String> roles) {
        this.id = id;
        this.userName = userName;
        this.password = password;
        this.roles = roles;
    }
}

首先呢是接口

public interface IUserService {
    SysUser findByUsername(String userName);
}

然后实现类

@Service
public class UserServiceImpl implements IUserService {

    private static final Set<SysUser> users = new HashSet<>();


    static {
        users.add(new SysUser(1L, "fulin", "123456", Arrays.asList("ROLE_ADMIN", "ROLE_DOCKER")));
        users.add(new SysUser(2L, "xiaohan", "123456", Arrays.asList("ROLE_ADMIN", "ROLE_DOCKER")));
        users.add(new SysUser(3L, "longlong", "123456", Arrays.asList("ROLE_ADMIN", "ROLE_DOCKER")));
    }

    @Override
    public SysUser findByUsername(String userName) {
        return users.stream().filter(o -> StringUtils.equals(o.getUserName(), userName)).findFirst().orElse(null);
    }
}

配置SpringSecurity

找到谁

要想通过Security的用户认证的话 必须要实现一个UserDetailsService的接口

@Service
public class UserService implements UserDetailsService {
    @Autowired
    private final IUserService iUserService;
    
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        SysUser user = iUserService.findByUsername(s);
        if (user == null) {
            throw new UsernameNotFoundException("用户不存在");
        }
        //把角色放入认证器里
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        List<String> roles = user.getRoles();
        for (String role : roles) {
            authorities.add(new SimpleGrantedAuthority(role));
        }
        return new User(user.getUserName(), user.getPassword(), authorities);
    }

}

这里呢要实现一个返回 UserDetails 类的方法 ,

// Source code recreated from a .class file by IntelliJ IDEA
public interface UserDetails extends Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();

    String getPassword();

    String getUsername();

    boolean isAccountNonExpired();

    boolean isAccountNonLocked();

    boolean isCredentialsNonExpired();

    boolean isEnabled();
}

里面与有各种的用户状态 这里呢

security默认有一个org.springframework.security.core.userdetails.User的实现 我们返回的时候构建这个类即可,它有三个参数,分别是用户名、密码和权限集。

这一骤只是为了让security找到你是谁。

登陆成功怎么办

security提供了一个AuthenticationSuccessHandler 的接口默认实现是跳转url,因为程序走ajax了所以我们返回json

@Component
@Slf4j
public class SuccessAuthenticationHandler implements AuthenticationSuccessHandler {
    private final ObjectMapper objectMapper;

    public SuccessAuthenticationHandler(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        log.info("登录成功");
        httpServletResponse.setStatus(HttpStatus.OK.value());
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        httpServletResponse.getWriter().write(objectMapper.writeValueAsString(authentication));
    }
}

登陆失败怎么办

与成功处理器对应的还有一个失败处理器

@Component
@Slf4j
public class FailureAuthenticationHandler implements AuthenticationFailureHandler {
    private final ObjectMapper objectMapper;

    public FailureAuthenticationHandler(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        log.info("登录失败");
        httpServletResponse.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        httpServletResponse.getWriter().write(objectMapper.writeValueAsString(e.getMessage()));
    }
}

WebSecurityConfig

上面做的那么多但是并没有跟security关联上

这时候我们要继承WebSecurityConfigurerAdapter 这个类来实现security的个性化配置

把我们自定义的 userDetailsServiceSuccessAuthenticationHandlerFailureAuthenticationHandler注入进来

这里我们还指定了密码的加密方式(5.0 版本强制要求设置),因为我们构造的数据是明文的,所以明文返回即可,如下所示:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private FailureAuthenticationHandler failureAuthenticationHandler;
    @Autowired
    private SuccessAuthenticationHandler successAuthenticationHandler;
    @Autowired
    private UserService userService;


    /**
     * 注入身份管理器bean
     *
     * @return
     * @throws Exception
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * 把userService 放入AuthenticationManagerBuilder 里
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(
                new PasswordEncoder() {
                    @Override
                    public String encode(CharSequence charSequence) {
                        return charSequence.toString();
                    }

                    @Override
                    public boolean matches(CharSequence charSequence, String s) {
                        return s.equals(charSequence.toString());
                    }
                });
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //http.httpBasic()  //httpBasic 登录
        http.formLogin()
                .loginPage("/login")// 登陆的url
                .loginProcessingUrl("/authentication/form") // 自定义登录路径
                .failureHandler(failureAuthenticationHandler) // 自定义登录失败处理
                .successHandler(successAuthenticationHandler) // 自定义登录成功处理
                .and()
                .logout()
                .logoutUrl("/logout")
                .and()
                .authorizeRequests()// 对请求授权
                 // 这些页面不需要身份认证,其他请求需要认证
                .antMatchers("/login", "/authentication/require",
                        "/authentication/form").permitAll()
                .anyRequest() // 任何请求
                .authenticated() // 都需要身份认证
                .and()
                .csrf().disable();// 禁用跨站攻击
    }
    @Override
    public void configure(WebSecurity web) throws Exception {
        // 设置拦截忽略文件夹,可以对静态资源放行
        web.ignoring().antMatchers("/css/**", "/js/**");
    }

}

如果你想要将密码加密,可以修改 configure() 方法如下

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
     auth.userDetailsService(userDetailsService)
         .passwordEncoder(new BCryptPasswordEncoder());
 }

然后启动项目会进入登陆页面,输入正确的用户名和密码即可。

本博文是基于springboot2.x 和security 5 如果有什么不对的请在下方留言。

你可能感兴趣的:(SpringBoot整合SpringSecurity(一)入门程序)