入门demo(二)表单认证

上一篇博客集成 Spring Security,使用其默认生效的 HTTP 基本认证保护 URL 资源,下面使用表单认证来保护 URL 资源。

一、默认表单认证:

代码改动:自定义WebSecurityConfig配置类

package com.security.demo.config;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
}

因为WebSecurityConfigurerAdapter的configure(HttpSecurity http)方法自带默认的表单身份认证,这里继承后不做方法修改,启动项目,这时访问localhost:8089/securityDemo/user/test仍然会跳转到默认的登陆页

二、自定义表单登陆:

1、自定义表单登陆页:

  代码改动:

(1)覆盖configure(HttpSecurity http)方法

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
 
	protected void configure(HttpSecurity http) throws Exception{
		http.authorizeRequests()
			.anyRequest().authenticated()
			.and()	
		.formLogin().
			loginPage("/myLogin.html")
			// 使登录页不设限访问
			.permitAll()
			.and().
		csrf().disable();
	}
}

(2)编写自定义的登陆页myLogin.html,放在resources/static/ 

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
<div class = "login" style="width:300px;height:300px">
    <h2>Acced Form</h2>
    <div class ="login-top"></div>
    <h1>LOGIN FORM</h1>
    <form action="myLogin.html" method="post">
        <input type="text" name="username" placeholder="username"/>
        <input type="password" name="password" placeholder="password"/>
        <div class="forgot" style="margin-top:20px;">
            <a href="#">forgot Password</a>
            <input type="submit" value="login">
        </div>
    </form>
    <div class="login-bottom">
        <h3>New User &nbsp;<a href ="">Register</a>&nbsp;&nbsp;</h3>
    </div>
</div>
</body>
</html>

访问localhost:8089/securityDemo/user/test会自动跳转到localhost:8089/securityDemo/static/myLogin.html

2、自定义登陆接口地址: 如自定义登陆接口为/login,代码改动:

(1)覆盖方法:

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
 
	protected void configure(HttpSecurity http) throws Exception{
		http.authorizeRequests()
			.anyRequest().authenticated()
			.and()
		.formLogin()
	    //	.loginPage("/myLogin.html")
			.loginProcessingUrl("/login")
			.permitAll()
			.and()
		.csrf().disable();
	}
}

(2)新增/login接口

package com.security.demo.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class Login {

    @RequestMapping("/login")
    public String login(String username,String password){
        System.out.println("用户名:"+username+",密码:"+password);
        return "登陆成功";
    }
}

重启后访问localhost:8089/securityDemo/user/test,自动跳转到spring默认的登陆页

输入user、控制台打印的密码,点击登陆按钮,可以看到调用了/login接口

调用成功后自动跳转到目标接口

注意:测试发现这个/login接口去掉也可以。

三、多用户身份认证

WebSecurityConfigurerAdapter配置文件在
configure(AuthenticationManagerBuilder auth)

方法中完成身份认证。前面的demo都只有一个用户,security中使用UserDetailsService做为用户数据源 ,所以可以实现UserDetailsService 接口来自定义用户。实现方法可以有几下几种:

1)内容用户

2)JDBC读取

3)自定义UserDetailsService

4)自定义AuthenticationProvider

3.1、使用内存用户验证InMemoryUserDetailsManager 

  代码改动:

package com.security.demo.config;

import org.springframework.security.crypto.password.PasswordEncoder;

public class MyPasswordEncoder implements PasswordEncoder {
    @Override
    public String encode(CharSequence charSequence) {
        return charSequence.toString();
    }

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

 配置类中configure(AuthenticationManagerBuilder auth)方法覆盖身份认证:

//身份认证
@Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        //可以设置内存指定的登录的账号密码,指定角色;不加.passwordEncoder(new MyPasswordEncoder())就不是以明文的方式进行匹配,会报错:java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
        auth.inMemoryAuthentication().passwordEncoder(new MyPasswordEncoder()).withUser("admin").password("123").roles("xtgly");
        auth.inMemoryAuthentication().passwordEncoder(new MyPasswordEncoder()).withUser("zs").password("123").roles("userAdmin","roleAdmin");
        auth.inMemoryAuthentication().passwordEncoder(new MyPasswordEncoder()).withUser("ls").password("123").roles("schoolAdmin");
        //加上.passwordEncoder(new MyPasswordEncoder())。页面提交时候,密码以明文的方式进行匹配。
    }

2、测试:重启项目控制台不再输出随机的默认密码,

输入正常的账号密码跳转到目标接口,输入错误的账号密码跳转到登陆错误页面。

3.2、JDBC方式:

   代码:

@Autowired
DataSource dataSource;
 
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.jdbcAuthentication()
            .dataSource(dataSource)
            // 下面的方法会运行数据表初始化脚本,前提是你的数据库支持varchar_ignorecase字段类型
            // .withDefaultSchema()
	        //使用自定义sql查询用户信息
	        .usersByUsernameQuery("select username,password,enabled from users " + "where username = ?")
            .withUser("tester")
            .password(passwordEncoder.encode("123456"))
            .authorities("tester")
            .and()
            .withUser("user")
            .password(passwordEncoder.encode("123456"))
            .authorities("tester");
}
3.3、 自定义UserDetailsService:
3.4、自定义AuthenticationProvider:

四、自定义表单认证与拦截:

这里配置了两个拦截器:

(1)登陆拦截LoginFilter:完成用户身份认证,认证成功返回token;根据前面的介绍,访问/login接口调用链路为:

(1)收集用户名密码:LoginFilter.attemptAuthentication ;
    (2)全局authenticationManager认证,内部调用UserDetailsService,无需代码实现
(3)认证逻辑:UserDetailsService.loadUserByUsername,返回UserDetails
(4)认证成功:LoginFilter.successfulAuthentication接收UserDetails,返回token

(2)授权拦截AuthenticationFilter:这里暂时不做介绍。

主要代码为:

1、总配置入口WebSecurityConfig:
package com.security.demo.config;
import com.security.demo.filter.AuthenticationFilter;
import com.security.demo.filter.LoginFilter;
import com.security.demo.util.RedisClient;
import org.springframework.beans.factory.annotation.Autowired;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private RedisClient redisClient;

    /**
     * 需要放行的URL
     */
    private static final String[] AUTH_WHITELIST = {
            "/static/**",
            "/index.html",
            "/login/loginSSO"
    };

    /**
     * 设置 HTTP 验证规则
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception{
       /* http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                //	.loginPage("/myLogin.html")
                .loginProcessingUrl("/login")
                .permitAll()
                .and()
                .csrf().disable();*/
        http.exceptionHandling()
                .and().csrf().disable()
                .authorizeRequests()
                .anyRequest().authenticated().and()
                //1、登陆、退出url,均由前端拦截器控制,这里注释掉。
                //1.1、前端拦截器中判断缓存token为空,为空则post请求访问/login,目的是进入LoginFilter获取token
                //1.2、不为空则带token访问接口,如果AuthenticationFilter拦截token不合法则根据错误码跳转到登陆页面,重复1.1的操作
                //.logout().logoutUrl("/logout").and()
                //2、身份认证filter,访问系统(除了白名单接口)需要先登陆。post请求/login接口会进入这个拦截器
                // 校验用户名密码是否正确,正确返回token给前端,不正确则返回异常信息
                .addFilter(new LoginFilter(authenticationManager(),redisClient))
                //3、授权filer,authenticationManager为BasicAuthenticationFilter的必传参数。所有的接口都会走到这里
                // 根据用户id查询权限,连同身份一起塞入SecurityContextHolder全局变量,后面获取用户信息则直接从SecurityContextHolder中get
                .addFilter(new AuthenticationFilter(authenticationManager(),redisClient,userDetailsService)).httpBasic();
    }


    /**
     * 身份认证
     * @param auth
     * @throws Exception
     */
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(new MyPasswordEncoder());
    }


    /**
     * 配置哪些请求不拦截
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers(AUTH_WHITELIST);
    }
}
2、登陆拦截器LoginFilter
package com.security.demo.filter;

import com.security.demo.constant.UserConstants;
import com.security.demo.dto.UserDTO;
import com.security.demo.util.JwtUtil;
import com.security.demo.util.RedisClient;
import org.apache.http.entity.ContentType;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.util.StringUtils;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Map;

/**
 * 登陆拦截器,身份认证
 */
public class LoginFilter extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;
    private RedisClient redisClient;

    public LoginFilter(AuthenticationManager authenticationManager,RedisClient redisClient) {
        this.authenticationManager = authenticationManager;
        this.redisClient = redisClient;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
            throws AuthenticationException {
        try {
            String userName = req.getParameter("userName");
            String passWord = req.getParameter("passWord");
            if (StringUtils.isEmpty(userName)) {
                throw new UsernameNotFoundException("请输入账号");
            }
            //验证用户名密码是否正确
            Map<String, String> userMap = UserConstants.getUsers();
           if(!userMap.keySet().contains(userName)){
               throw new UsernameNotFoundException("用户不存在");
           }
           if(!passWord.equals(userMap.get(userName))){
               throw new UsernameNotFoundException("密码错误");
           }
           //这里权限返回空,由后面的授权过滤器查询
            return authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(userName, passWord, new ArrayList<>()));
        } catch (UsernameNotFoundException e) {
            //返回错误信息
            res.setCharacterEncoding("UTF-8");
            res.setContentType("application/text;charset=utf-8");
            try {
                res.getWriter().write(e.getMessage());
            } catch (IOException e1) {
                e1.printStackTrace();
            }
            return null;
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }
    /**
     * 登录成功(即UserDetailsService正常返回)调用的方法,
     * 这里返回token给前端
     * @param req
     * @param res
     * @param chain
     * @param auth
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain,
                                            Authentication auth) throws IOException, ServletException {
        UserDTO userDTO = (UserDTO) auth.getPrincipal();
        String userName = userDTO.getUsername();
        String password = userDTO.getPassword();
        String jwtToken = JwtUtil.sign(userName, password);
        //缓存到redis中
        redisClient.set(userName,jwtToken);
        //返回
        res.setContentType(ContentType.TEXT_HTML.toString());
        res.getWriter().write(jwtToken);
    }
}
3、MyUserDetailsService 用户认证服务
package com.security.demo.service;

import com.security.demo.constant.UserConstants;
import com.security.demo.dto.UserDTO;
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.Service;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.Map;

/**
 * 当什么也没有配置的时候,账号和密码是由 Spring Security 定义生成的。
 * 而在实际项目中账号和密码都是从数据库中查询出来的。所以我们要通过自定义逻辑控制认证逻辑,
 */
@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //模拟数据库查询
        Map<String, String> userMap = UserConstants.getUsers();
        String dbPwd = userMap.get(username);
        if(StringUtils.isEmpty(dbPwd)){
            throw new UsernameNotFoundException("用户不存在");
        }
        Map<String, List<String>> userRoles = UserConstants.getUserRoles();
        List<String> roles = userRoles.get(username);
        Map<String, List<String>> userMenus = UserConstants.getUserPermissions();
        List<String> menus = userMenus.get(username);
        UserDTO userDTO = new UserDTO(null,null,username,roles,menus,dbPwd);
        return userDTO;
    }
}
4、UserDTO用户主体:
package com.security.demo.dto;

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

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

public class UserDTO implements UserDetails {

    private Integer id;
    private String userName;
    private String userAccount;
    private List<String> roles;
    private List<String> menus;
    private String passWord;

    public UserDTO (Integer id,String userName,String userAccount,List<String> roles,List<String> menus,String passWord){
        this.id = id;
        this.userAccount = userAccount;
        this.userName = userName;
        this.roles = roles;
        this.menus = menus;
        this.passWord = passWord;
    }

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

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

    @Override
    public String getUsername() {
        return this.userAccount;
    }

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

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

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

    @Override
    public boolean isEnabled() {
        return true;
    }
}
5、测试:

(1)用户名为空

(2)输入错误的用户

(3)输入错误的密码

(4)输入正确的账号密码

  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
知识图谱入门demo是指针对知识图谱技术的初学者制作的演示项目或示例,旨在帮助用户了解知识图谱的基本概念和实际应用。 一个通常的知识图谱入门demo可能包括以下内容: 1. 数据采集与清洗:首先,需要选择一个特定的主题或领域,收集与该领域相关的数据。这些数据可以来自于公开数据库、互联网上的文本或结构化数据等多种来源。然后进行数据清洗,通过文本分析、实体抽取等技术将原始数据转化为结构化的知识表示形式。 2. 知识表示与建模:在demo中,可以选择常用的知识图谱表示方法,如RDF(资源描述框架)或OWL(Web本体语言),将收集到的数据转换为图谱的节点和边的形式。节点代表实体或概念,边表示实体或概念之间的关系。 3. 知识图谱的查询与推理:为了展示知识图谱的查询与推理能力,可以设计一些基本的问题或查询,如通过图谱找到特定实体的属性信息、通过关联关系找到相关的实体等。同时,也可以利用推理算法,发现隐藏在知识图谱中的隐含知识,提供更加丰富的查询结果。 4. 可视化与交互界面:为了更好地展示和使用知识图谱,一个入门demo通常会包含一个用户界面,通过可视化的方式展示知识图谱的结构和内容,并提供查询和筛选等交互操作。 通过参与知识图谱入门demo,用户可以在实践中了解知识图谱的基本原理、构建过程和实际应用。同时,也可以通过与其他学习者和专业人士的交流,进一步深入学习和研究知识图谱技术。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

w_t_y_y

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

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

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

打赏作者

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

抵扣说明:

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

余额充值