Spring Security基本使用初体验01

Spring Security最主要的作用就是就是认证安全登录鉴权。Spring Security其核心就是一组过滤器链,项目启动后将会自动配置。最核心的就是 Basic Authentication Filter 用来认证用户的身份,一个在Spring Security中一种过滤器就处理一种认证方式,一种过滤器按照自身职责判定是否是自身需要的信息,认证通过就进入下一环节,直至最后认证通过。

1. Spring Security基本使用体验

创建Spring boot项目,首先不引入Spring Security框架,创建最简单的web项目。

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

添加两个基础的页面,在template文件夹下,创建home.html页面。
home.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
    <title>home页面</title>
</head>
<body>
<h1>Welcome!,来到home页面</h1>
<p>
    Click
    <a th:href="@{/hello}">here</a>
    to see a greeting.
</p>
</body>
</html>

hello.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
    <title>hello页面</title>
</head>
<body>
<h1>Hello world!</h1>
</body>
</html>

点击home页面中的 “/hello” 链接跳转到 hello页面。这里首先需要添加控制器,完成跳转,这里就配置视图控制器来暴露这些页面。

/**
 * spring mvc 视图控制器
 */
@Configuration
public class MvcConfig implements WebMvcConfigurer {


    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/home").setViewName("home");
        registry.addViewController("/").setViewName("home");
        registry.addViewController("/hello").setViewName("hello");
    }
}

启动项目,就可以通过http://localhost:8080/home访问home页面,然后通过该页面的链接跳转至hello页面。
在这里插入图片描述
接着加入Spring Security依赖,为项目添加安全权限功能。

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>

直接引入依赖后,Spring Security就已经起作用了;启动项目后,再次访问http://localhost:8080/home,会直接跳转到Spring Security自动生成的login页面。可以看见我们自己创建的页面接口已经被保护起来了。
在这里插入图片描述
可以通过Spring Security进行安全配置,可以使得只有认证过的用户才可以访问到受保护的页面,也可以指定哪些页面访问可以放行。

/**
 * spring security 安全配置项
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/", "/home")
                .permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
                .logout()
                .permitAll();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .inMemoryAuthentication()
                .passwordEncoder(new BCryptPasswordEncoder())
                .withUser("user")
                .password(new BCryptPasswordEncoder().encode("123456"))
                .roles("USER");
    }
}

WebSecurityConfig类使用了@EnableWebSecurity注解 ,以启用Spring Security的Web 安全支持。

configure() 方法定义了哪些URL路径应该被保护,哪些不需要保护直接放行。“/” 和 “/home” 路径被配置为不需要任何身份验证。其余的路径必须经过身份验证才能访问。 当用户成功登录时,它们将被重定向到先前请求的需要身份认证的页面。

configureGlobal() 方法,它将单个用户设置在内存中。该用户的用户名为 “user”,密码为 “123456”,角色为 “USER”。

创建login.html页面,用于替代自定义的login页面。

<!DOCTYPE html>
<html
        xmlns="http://www.w3.org/1999/xhtml"
        xmlns:th="http://www.thymeleaf.org"
        xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"
>
<head>
    <title>Spring Security Example</title>
</head>
<body>
<div th:if="${param.error}">Invalid username and password.</div>
<div th:if="${param.logout}">You have been logged out.</div>
<form th:action="@{/login}" method="post">
    <div>
        <label>
            User Name :
            <input type="text" name="username" />
        </label>
    </div>
    <div>
        <label>
            Password:
            <input type="password" name="password" />
        </label>
    </div>
    <div><input type="submit" value="Sign In" /></div>
</form>
</body>
</html>

启动项目后,访问http://localhost:8080/login。
在这里插入图片描述
该页面提供了一个表单来获取用户名和密码,并将它们提交到 “/login”。 根据前面的配置,Spring Security提供了一个拦截该请求并验证用户的过滤器。 如果用户未通过认证,该页面将重定向到 “/login?error”,并在页面显示相应的错误消息;如果用户通过认证就会进入home页面。

前面添加了注销登录配置,logout() 方法就是用户注销功能。注销成功后,应用程序会默认跳转到 “/login?logout”,就是前面的登录页面。修改下 hello.html 页面,添加一个logout链接。

<h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
<form th:action="@{/logout}" method="post">
    <input type="submit" value="Sign Out"/>
</form>

在这里插入图片描述
在这里插入图片描述

可以看到点击退出登陆就会跳转至login页面,到这里Spring Security最基本的使用流程就完成了。

2.用户认证

进行用户认证,第一步就是需要获取用户的信息。Spring Security提供了 UserDetailsService接口来获取用户信息。这个接口用户加载用户的信息,提供了一个只读方法,便于简化对新的访问策略的支持。

创建MyUserDetailsService实现UserDetailsService,实现读取用户信息的方法。这样就能让自定义的UserdetailsService生效了,接着我们就可以从这里获取用户的信息。

在WebSecurityConfig中声明对密码加密的类。

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
/**
 自定义数据源来获取数据
 这里只要是存在一个自定义的UserDetailsService,那么Spring Security将会使用该实例进行配置
 */
@Component
public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;

    // 可以从任何地方获取数据,比如内存中或者数据库
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        System.out.println("获取到的用户信息:" + username);


        String password = passwordEncoder.encode("123456");

        return new User(username, password,
                AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
    }
}

去除前面设置在内存中的用户信息,此时这里不再从哪里获取,而是从自定义的MyUserDetailsService中获取。
在这里插入图片描述
启动项目就可以使用admin/123456进行登陆了。

2.1 前后端分离模式的用户认证

在前面登录验证时都是统一跳转到了一个登录的html页面上去;在前后分离的情况下,一般都是给前端一个JSON串,让前端人员去判断处理。具体的处理方法是在完成身份验证后,不再直接跳转到页面,而是跳转到Controller中。在Controller中完成后续的逻辑。

添加依赖

        <!--json相关依赖-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.49</version>
        </dependency>

        <!--lombok组件-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.0</version>
        </dependency>

修改Spring Security的安全配置

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/authentication/*", "/", "/home", "/login")
                .permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()
                //.loginPage("/login")
                // 更换成自定义的一个真实存在的处理器地址
                .loginPage("/authentication/require")
                .loginProcessingUrl("/authentication/form")
                // 加入自定义处理器
                .successHandler(myAuthenticationSuccessHandler)
                .permitAll()
                .and()
                // csrf 防护关掉
                .csrf().disable()
                .logout()
                .permitAll();

    }

这里不再是跳转至login页面了,而是跳转至一个处理地址,需要创建一个控制器处理处理这个请求。

创建RestResult.java用于封装结果返回信息。

public class RestResult {

    // 定义jackson对象
    private static final ObjectMapper MAPPER = new ObjectMapper();

    private String status;
    private String msg;
    private Object data;

    public static RestResult build(String status, String msg, Object data) {
        return new RestResult(status, msg, data);
    }


    public static RestResult ok(Object data) {
        return new RestResult(data);
    }

    public static RestResult ok() {
        return new RestResult(null);
    }

    public RestResult() {

    }

    public static RestResult build(String status, String msg) {
        return new RestResult(status, msg, null);
    }

    public RestResult(String status, String msg, Object data) {
        this.status = status;
        this.msg = msg;
        this.data = data;
    }

    public RestResult(Object data) {
        this.status = "200";
        this.msg = "OK";
        this.data = data;
    }

    public Boolean isOK() {
        return this.status == "200";
    }

    /**
     * 有object对象的转化
     * @param jsonData
     * @param clazz
     * @return
     */
    public static RestResult formatToPojo(String jsonData, Class<?> clazz) {
        
        try {
            if (clazz == null) {
                return MAPPER.readValue(jsonData, RestResult.class);
            }
            JsonNode jsonNode = MAPPER.readTree(jsonData);
            JsonNode data = jsonNode.get("data");
            Object obj = null;
            if (clazz != null) {
                if (data.isObject()) {
                    obj = MAPPER.readValue(data.traverse(), clazz);
                } else if (data.isTextual()) {
                    obj = MAPPER.readValue(data.asText(), clazz);
                }
            }
            return build(jsonNode.get("status").asText(), jsonNode.get("msg").asText(), obj);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 没有object对象的转化
     * @param json
     * @return
     */
    public static RestResult format(String json) {
        try {
            return MAPPER.readValue(json, RestResult.class);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * Object是集合转化
     * @param jsonData
     * @param clazz
     * @return
     */
    public static RestResult formatToList(String jsonData, Class<?> clazz) {
        try {
            JsonNode jsonNode = MAPPER.readTree(jsonData);
            JsonNode data = jsonNode.get("data");
            Object obj = null;
            if (data.isArray() && data.size() > 0) {
                obj = MAPPER.readValue(data.traverse(),
                        MAPPER.getTypeFactory().constructCollectionType(List.class, clazz));
            }
            return build(jsonNode.get("status").asText(), jsonNode.get("msg").asText(), obj);
        } catch (Exception e) {
            return null;
        }
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }
}

创建SecurityRequestController.java处理/authentication/require请求。

@RestController
public class SecurityRequestController {
    
    private RequestCache requestCache = new HttpSessionRequestCache();

    // spring的工具类:封装了所有跳转行为策略类
    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    /**
     * 当需要身份认证时跳转到这里
     */
    @RequestMapping("/authentication/require")
    @ResponseStatus(code = HttpStatus.UNAUTHORIZED)
    public RestResult requirAuthentication(HttpServletRequest request, HttpServletResponse response) throws IOException {

        SavedRequest savedRequest = requestCache.getRequest(request, response);
        // 如果有引发认证的请求
        if (savedRequest != null) {
            String targetUrl = savedRequest.getRedirectUrl();
            System.out.println(("引发跳转的请求:" + targetUrl));
            // 如果是html请求,则跳转到登录页
            if (StringUtils.endsWithIgnoreCase(targetUrl, ".html")) {
                redirectStrategy.sendRedirect(request, response, "/login");
            }
        }
        // 否则都返回需要认证的json串
        return new RestResult("访问受限,请前往登录页面");
    }
}

创建自定义处理器MyAuthenticationSuccessHandler.java

/**
 * formLogin().successHandler() 中需要的处理器类型
 */
@Component("myAuthenticationSuccessHandler")
public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    // spring 是使用jackson来进行处理返回数据的
    // 所以这里可以得到他的实例
    @Autowired
    private ObjectMapper objectMapper;

    private final static String LoginType = "JSON";

    /**
     * @param authentication 封装了所有的认证信息
     */
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

        if (LoginType == "JSON") {
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(objectMapper.writeValueAsString(authentication));
        } else {
            super.onAuthenticationSuccess(request, response, authentication);
        }
    }
}

修改home页面的内容。

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
    <title>home页面</title>
</head>
<body>
<h1>Welcome!,来到home页面</h1>
<p>
    Click
    <a th:href="@{/hello.html}">here</a>
    to see a greeting.
</p>
</body>
</html>

修改视图控制器。

public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/home").setViewName("home");
        registry.addViewController("/").setViewName("home");
        registry.addViewController("/hello.html").setViewName("hello");
        registry.addViewController("/login").setViewName("login");
    }

启动项目,访问http://localhost:8080/home,会跳转至home页面,点击链接就会跳转至登陆页面。这里请求带html后缀就会跳转至login页面。

接着使用非html后缀的请求,这里就会返回json字符串。
在这里插入图片描述
这里的/authentication/form是登陆请求,我们使用postman进行登陆访问,注意这里是post请求。
在这里插入图片描述
demo源码下载:demo源码

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

picacho_pkq

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值