SpringSecurity-zig

httpBasic 认证模式

可以被破解,还是有一定安全作用

  1. 需要的依赖(核心)
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
  1. 配置(java)
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.httpBasic()
                .and()
                .authorizeRequests()
                .anyRequest()
                .authenticated(); // 所有的请求都需要登录认证
    }
}
  1. 修改登录账号和密码
spring:
  security:
    user:
      name: admin
      password: admin

PostMan工具

请添加图片描述

PasswordEncoder接口

hash算法

public interface PasswordEncoder {
    String encode(CharSequence var1);

    boolean matches(CharSequence var1, String var2);

    // 可以确定一个固定时间修改密码的规则
    default boolean upgradeEncoding(String encodedPassword) {
        return false;
    }
}

推荐使用的实现类:BCryptPasswordEncoder
在这里插入图片描述

formLogin模式登录

  • formLogin模式不需要写controller方法
  • formLogin登录认证,UsernamePasswordAuthenticationFilter(这个过滤器是默认继承的,我们只需要配置)

配置三要素:

  • 登录认证逻辑-登录url,如何接受登录参数,登录成功后的逻辑(静态)
  • 资源访问控制-决定什么用户、什么角色可以访问什么样的资源(动态-数据库)
  • 用户角色权限-配置某个用户拥有什么角色、角色拥有什么权限(动态-数据库)

在这里插入图片描述

代码部分

<h1>字母哥业务系统登录</h1>
<form action="/login" method="post">
    <span>用户名称</span><input type="text"  name="username"/> <br>
    <span>用户密码</span><input type="password"  name="password"/> <br>
    <input type="submit" onclick="login()" value="登陆">
</form>
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private MyAuthenticationFailureHandler myAuthenticationFailureHandler;
    @Resource
    private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 登录认证逻辑
        http.csrf().disable()
            .formLogin()
            .loginPage("/login.html")
            .loginProcessingUrl("/login")
            .usernameParameter("username")
            .passwordParameter("password")
            .defaultSuccessUrl("/")
           .failureUrl("/login.html")
                .and()
            .authorizeRequests()
                .antMatchers("/login.html", "/login").permitAll() //公开资源
                .antMatchers("/","/biz1","biz2") //匹配资源
                .hasAnyAuthority("ROLE_user","ROLE_admin") //有权限
                .antMatchers("/syslog").hasAuthority("sys:log")
                .antMatchers("/sysuser").hasAuthority("sys:user")
            .anyRequest()
            .authenticated();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication() //从内存中读取
                .withUser("user")
                .password(passwordEncoder().encode("123456"))
                .roles("user")
                .authorities("sys:log", "sys:user")
                .and()
                .withUser("admin")
                .password(passwordEncoder().encode("123456"))
                .roles("admin")
                .authorities("sys:log","sys:user", "ROLE_user", "ROLE_admin")
                .and()
                .passwordEncoder(passwordEncoder()); // 配置Bcypt加密,会自动调用match方法判断
    }

    // 将项目中的静态资源路径开放出来
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/css/**","/fonts/**","/img/**","/js/**");
    }
}

源码分析

SecurityContextPersistenceFilter:作为响应的最后一个过滤器,会将登录信息存储到session中,下次认证的时候就直接欧聪session中取信息。

过滤器验证的细节:
在这里插入图片描述

自定义登录认证结果处理

自定义登录验证结果处理的场景

  • 不同的人登录后看到不同的首页
  • 前后端分离,期望的响应结果是json 而不是html

重要接口

AuthenticationSuccessHandler接口:用于处理登录成功的接口,常用的实现类SavedRequestAwareAuthenticationSuccessHandler

AuthenticationFailureHandler接口:用于处理登录失败的接口,常用的实现类SimpleUrlAuthenticationFailureHandler

@Component
public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    // springboot 默认继承的一个类  json 和 类的相互转换
    private static final ObjectMapper objectMapper = new ObjectMapper();
    @Value("${spring.security.logintype}")
    private String loginType;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
            throws ServletException, IOException {
        if (loginType.equalsIgnoreCase("JSON")) {
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(objectMapper.writeValueAsString(AjaxResponse.success()));
        } else {
            super.onAuthenticationSuccess(request, response, authentication);
        }
    }
}

重要方法

    @Resource
    private MyAuthenticationFailureHandler myAuthenticationFailureHandler;
    @Resource
    private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;


http.csrf().disable()
            .formLogin()
            .loginPage("/login.html")
            .loginProcessingUrl("/login")
            .usernameParameter("uname")
            .passwordParameter("pword")
.successHandler(myAuthenticationSuccessHandler)
.failureHandler(myAuthenticationFailureHandler)

前端 js

<form action="/login" method="post">
    <span>用户名称</span><input type="text"  id="username"/> <br>
    <span>用户密码</span><input type="password"  id="password"/> <br>
    <input type="submit" onclick="login()" value="登陆">
</form>
<script text="text/javascript">
    function login() {
        let username = $("#username").val();
        let password = $("#password").val();
        if(username === "" || password === "") {
            alert("用户名或密码不能为空");
            return;
        }
        $.ajax({
            type: "POST",
            url: "/login",
            data: {
                "uname": username, // uname与usernameParameter的参数对应 ,pword同理
                "pword": password
            },
            success: function (json) {
                if (json.isok) {
                    location.href = '/';
                } else {
                    alert(json.message);
                    location.href = 'login.html';
                }
            },
            error: function (e){
                alert("发生了错误");
            }
        });
    }
</script>

Session回话的安全管理

SpringSecurity创建session 的策略:

  • always:如果当前请求没有对应的session,就创建一个
  • ifRequired(default):在需要使用的时候才创建
  • never:永远不会主动创建,但是如果有session则使用
  • stateless:不会创建和使用任何session(适合接口型应用,前后端分离,节省内存资源)
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);

设置会话超时:

  • servlet.servlet.session.timeout=15m
  • spring.session.timeout=15m ,引入springsession包,优先级更高

session保护:

  • migrationSession保护方式(默认),即对于同一个sessionId用户,每一次登录验证将创建一个新的HttpSession,就的HttpSession将无效,将旧的session属性复制到新的session属性上。

    • 设置为none时,原始回话不会无效
    • 设置newSession后,将创建一个干净的回话,而不会复制就会话中的任何属性
  • http.sessionManagement().sessionFixation().migrateSession(); // 默认配置
    

cookie的安全

  • httpOnly:如果为true,则浏览器脚本无法访问cookie
  • secure:如果为true,则仅通过https连接发送cookie,http无法携带cookie
server.servlet.session.cookie.http-only=true
server.servlet.session.cookie.secure=true

同账号多端登录踢下线

限制最大登录用户数量

http.sessionManagement().maximumSessions(1)
        .maxSessionsPreventsLogin(false)
        .expiredSessionStrategy(new CustomerSessionInformationExpiredStrategy());

限制策略

1.第一种策略:直接返回一个跳转的页面

public class CustomerSessionInformationExpiredStrategy implements SessionInformationExpiredStrategy {

    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
    @Override
    public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
        redirectStrategy.sendRedirect(event.getRequest(), event.getResponse(), "/invalid"); // 跳转到页面上,进行一个友好信息的提示
    }
}
  1. 第二种策略:返回一个json(前后端分离)
private ObjectMapper objectMapper = new ObjectMapper();@Overridepublic void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {    Map<String, Object> map = new HashMap<>();    map.put("code", 403);    map.put("msg", "你已经在另一台机器上登录,你被强制下线" + event.getSessionInformation().getLastRequest());    String json = objectMapper.writeValueAsString(map);    event.getResponse().setContentType("application/json;charset=utf-8");    event.getResponse().getWriter().write(json);}

RBAC权限管理模型

核心:静态配置转换为动态加载

用户:系统接口及功能访问的操作者

权限:能够访问某接口或者做某操作的授权资格

角色:具有一类相同操作权限的用户的总称

重要的接口

UserDetails:表达的是你是谁,你有什么权限。pojo类的get方法给springsecurity调用,set方法程序员用来赋值

UserDetailsService:表达的是如何动态的加载UserDetails数据

动态加载用户权限

@NoArgsConstructor@AllArgsConstructor@Setter@ToStringpublic class User implements UserDetails {    private int id;    private String username;    private String password;    private Date creatTime;    private Date lastLoginTime;    private List<GrantedAuthority> authorityList = new ArrayList<>();    @Override    public Collection<? extends GrantedAuthority> getAuthorities() {        return authorityList;    }    @Override    public String getPassword() {        return password;    }    @Override    public String getUsername() {        return username;    }    @Override    public boolean isAccountNonExpired() {        return true;    }    @Override    public boolean isAccountNonLocked() {        return true;    }    @Override    public boolean isCredentialsNonExpired() {        return true;    }    @Override    public boolean isEnabled() {        return true;    }}
@Servicepublic interface UserService extends UserDetailsService {    User findUserByUsername(String username);    List<Permission> findAllPermissionByUsername(String username);    User queryUserByUsername(String username);}
@Service("UserServiceImpl")
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper mapper;

    @Override
    public User findUserByUsername(String username) {
        return mapper.findUserByUsername(username);
    }

    @Override
    public List<Permission> findAllPermissionByUsername(String username) {
        return mapper.findAllPermissionByUsername(username);
    }

    @Override
    public User queryUserByUsername(String username) {
        return mapper.queryUserByUsername(username);
    }

    /**
     *
     * @param username 唯一的标识, 通过唯一的标识加载数据
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 用户基础数据
        User user = mapper.findUserByUsername(username);
        if(user == null){
            throw new UsernameNotFoundException("用户名不存在");
        }
        // 用户权限数据
        List<Permission> authorities = mapper.findAllPermissionByUsername(username);
        user.setAuthorityList(AuthorityUtils.commaSeparatedStringToAuthorityList(String.join(",", (CharSequence) authorities)));

        return null;
    }
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
}

动态加载资源鉴权规则

配置三要素:

  • 登录认证逻辑——登录URL(静态)
  • 用户角色权限——配置某个用户拥有什么角色,什么权限(动态)
  • 资源鉴权规则——决定什么用户,什么角色可以访问什么资源(动态)
// 对请求进行授权过滤
@Service("rbacService")
public class MyRBACService {
    public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
        Object principal = authentication.getPrincipal(); //当前类型的主体信息
        if (principal instanceof UserDetails) {
            UserDetails userDetails = (UserDetails) principal;
            // 本次要访问的资源
            SimpleGrantedAuthority authority = new SimpleGrantedAuthority(request.getRequestURI());
            return userDetails.getAuthorities().contains(authority);
        }
        return false;
    }
}
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private MyAuthenticationFailureHandler myAuthenticationFailureHandler;
    @Resource
    private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;

    @Autowired
    @Qualifier("UserServiceImpl")
    private UserService userService;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    // 用于 对用户的请求授权 等操作
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.formLogin().loginPage("/login.html").loginProcessingUrl("/login").usernameParameter("username").passwordParameter("password")
                .successHandler(myAuthenticationSuccessHandler).failureHandler(myAuthenticationFailureHandler)
                .and()
                .authorizeRequests().antMatchers("/login.html", "/login").permitAll()
                .anyRequest().access("@rbacService.hasPermission(request, authentication)");
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                .sessionFixation().migrateSession().maximumSessions(1).maxSessionsPreventsLogin(false)
                .expiredSessionStrategy(new CustomerSessionInformationExpiredStrategy());
    }

    // 用于登录
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
    }

}

tips:

  1. 向一个接口注入它的实现类的方法(取名字)

    @Service("UserServiceImpl")
    public class UserServiceImpl implements UserService {
    }
    
    @Autowired
    @Qualifier("UserServiceImpl")
    private UserService userService;
    

常见Request获取URL的方法

request.getRequestURL():返回的是完整的url,包括Http协议,端口号,servlet名字和映射路径,但它不包含请求参数。

request.getRequestURI():得到的是request URL的部分值,并且web容器没有decode过的

request.getContextPath():返回 the context of the request.

request.getServletPath():返回调用servlet的部分url.

request.getQueryString():返回url路径后面的查询字符串

SpEL 权限表达式

SpEL:Spring Expression Language

(11条消息) 第5部分:表达式语言SpEL_Jack Zhou的专栏-优快云博客_spel表达式

SpEL表达式总结 - 简书 (jianshu.com)

在这里插入图片描述

在这里插入图片描述

RememberMe功能

从内存

在这里插入图片描述

http.rememberMe(); // 开启rememberMe功能
<form action="/login" method="post">
    <span>用户名称</span><input type="text"  id="username"/> <br>
    <span>用户密码</span><input type="password"  id="password"/> <br>
    <input type="button" onclick="login()" value="登陆">
    <label><input type="checkbox" name="remember-me" id="remember-me"/>记住密码</label>
</form>
<script text="text/javascript">
    function login() {
        let username = $("#username").val();
        let password = $("#password").val();
        let rememberMe = $("#remember-me").is(":checked");
        if(username === "" || password === "") {
            alert("用户名或密码不能为空");
            return;
        }
        $.ajax({
            type: "POST",
            url: "/login",
            data: {
                "username": username,
                "password": password,
                // 目前名字一定要是 remember-me
                "remember-me": rememberMe
            },
            success: function (json) {
                if (json.isok) {
                    location.href = '/';
                } else {
                    alert(json.message);
                    location.href = 'login.html';
                }
            },
            error: function (e){
                alert("发生了错误");
            }
        });
    }
</script>

修改 rememberme 相关参数名称

http.rememberMe().rememberMeParameter("remember-me"); // 开启rememberMe功能, rememberMeParameter()可以设置前端页面的参数
http.rememberMe().rememberMeCookieName("rememberMeNew");; // 开启rememberMe功能, rememberMeParameter()可以设置前端页面的参数
http.rememberMe().tokenValiditySeconds(int); //设置rememberme的功能时限

从数据库

在这里插入图片描述

代码实现

  1. 准备数据库表

    CREATE TABLE `persistent_logins`(
    	`username`VARCHAR(64) NOT NULL,
    	`series` VARCHAR(64) NOT NULL,
    	`token` VARCHAR(64) NOT NULL,
    	`last_used` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    	PRIMARY KEY(`series`)
    ) ENGINE=INNODB DEFAULT CHARSET=utf8;
    
  2. java代码

    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Resource
        private DataSource dataSource;
    
        @Resource
        private MyAuthenticationFailureHandler myAuthenticationFailureHandler;
        @Resource
        private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
    
        @Autowired
        @Qualifier("UserServiceImpl")
        private UserService userService;
    
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.rememberMe().rememberMeParameter("remember-me").rememberMeCookieName("rem-me-cookie")
                    .tokenValiditySeconds(2 * 24 * 60).tokenRepository(persistentTokenRepository()); // 
            http.csrf().disable();
            http.formLogin().loginPage("/login.html").loginProcessingUrl("/login").usernameParameter("username").passwordParameter("password")
                    .successHandler(myAuthenticationSuccessHandler).failureHandler(myAuthenticationFailureHandler)
                    .and()
                    .authorizeRequests().antMatchers("/login.html", "/login").permitAll()
                    .anyRequest().access("@rbacService.hasPermission(request, authentication)");
            http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                    .sessionFixation().migrateSession().maximumSessions(1).maxSessionsPreventsLogin(false)
                    .expiredSessionStrategy(new CustomerSessionInformationExpiredStrategy());
        }
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
        }
    
        @Bean
        public PersistentTokenRepository persistentTokenRepository() {
            JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
            jdbcTokenRepository.setDataSource(dataSource);
            return jdbcTokenRepository;
        }
    }
    
    

用户退出登录

两行代码

<a href="/logout">退出</a>
http.logout();

在这里插入图片描述

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private DataSource dataSource;

    @Resource
    private MyAuthenticationFailureHandler myAuthenticationFailureHandler;
    @Resource
    private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;

    @Autowired
    @Qualifier("UserServiceImpl")
    private UserService userService;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.rememberMe().rememberMeParameter("remember-me").rememberMeCookieName("rem-me-cookie")
                .tokenValiditySeconds(2 * 24 * 60).tokenRepository(persistentTokenRepository()); // 开启rememberMe功能, rememberMeParameter()可以设置前端页面的参数
        http.csrf().disable();
        http.formLogin().loginPage("/login.html").loginProcessingUrl("/login").usernameParameter("username").passwordParameter("password")
                .successHandler(myAuthenticationSuccessHandler).failureHandler(myAuthenticationFailureHandler)
                .and()
                .authorizeRequests().antMatchers("/login.html", "/login").permitAll()
                .anyRequest().access("@rbacService.hasPermission(request, authentication)");
        http.logout();
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                .sessionFixation().migrateSession().maximumSessions(1).maxSessionsPreventsLogin(false)
                .expiredSessionStrategy(new CustomerSessionInformationExpiredStrategy());
    }

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

    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        return jdbcTokenRepository;
    }

}

个性化操作:

即除了四个默认操作,增加新的操作

http.logout().logoutUrl("/logout").logoutSuccessUrl("/").deleteCookies("JESSIONID");

logoutSuccessHandler

@Component
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication auth) throws IOException, ServletException {
        // 退出登录业务的逻辑(比如登录时间统计)

        response.sendRedirect("/login.html");
    }
}
@Override
protected void configure(HttpSecurity http) throws Exception {
  http.logout().logoutUrl("/logout").logoutSuccessHandler(myLogoutSuccessHandler).deleteCookies("JESSIONID");
    // 其他配置
  
}

图片验证码

方式一

谜面用于展现,谜底用于验证。

在这里插入图片描述

大致过程:

  1. 浏览器发起请求:验证码谜面
  2. 服务器响应:生产验证码谜底:session,生产验证码图片
  3. 浏览器发出登录请求
  4. 服务器响应:验证用户的输入:是否和session内验证码数据一致。不一致则重新生成验证码

开发三部曲:

  • 验证码配置工具
  • 验证码加载(重点)
  • 验证码校验(重点)
  1. 配置

引入依赖

<dependency>
   <groupId>com.github.penggle</groupId>
   <artifactId>kaptcha</artifactId>
   <version>2.3.2</version>
   <exclusions>
      <exclusion>
         <groupId>javax.servlet-api</groupId>
         <artifactId>javax.servlet</artifactId>
      </exclusion>
   </exclusions>
</dependency>

配置一般是properties

kaptcha.border=no
kaptcha.border.color=105,179,90
kaptcha.image.width=100
kaptcha.image.height=45
kaptcha.session.key=code
kaptcha.textproducer.font.color=blue
kaptcha.textproducer.font.size=35
kaptcha.textproducer.char.length=4
kaptcha.textproducer.font.name=宋体,楷体,微软雅黑

注入到springboot

@PropertySource(value = {"classpath:kaptcha.properties"})
public class CaptchaConfig {
    @Value("${kaptcha.border}")
    private String border;
    @Value("${kaptcha.border.color}")
    private String borderColor;
    @Value("${kaptcha.image.width}")
    private String imageWidth;
    @Value("${kaptcha.image.height}")
    private String imageHeight;
    @Value("${kaptcha.session.key}")
    private String sessionKey;
    @Value("${kaptcha.textproducer.font.color}")
    private String fontColor;
    @Value("${kaptcha.textproducer.font.size}")
    private String fontSize;
    @Value("${kaptcha.textproducer.char.length}")
    private String charLength;
    @Value("${kaptcha.textproducer.font.name}")
    private String fontNames;


    @Bean(name = "kaptchaProducer")
    public DefaultKaptcha getKaptchaBean() {
        DefaultKaptcha kaptcha = new DefaultKaptcha();
        Properties properties = new Properties();
        properties.setProperty("kaptcha.border", border);
        properties.setProperty("kaptcha.border.color", borderColor);
        properties.setProperty("kaptcha.image.width", imageWidth);
        properties.setProperty("kaptcha.border.height", imageHeight);
        properties.setProperty("kaptcha.session.key", sessionKey);
        properties.setProperty("kaptcha.textproducer.font.color", fontColor);
        properties.setProperty("kaptcha.textproducer.font.size", fontSize);
        properties.setProperty("kaptcha.textproducer.char.length", charLength);
        properties.setProperty("kaptcha.textproducer.font.name", fontNames);
        kaptcha.setConfig(new Config(properties));
        return kaptcha;
    }
}
  1. 验证码加载

前端

<h1>字母哥业务系统登录</h1>
<form action="/login" method="post">
    <span>用户名称</span><input type="text"  id="username"/> <br>
    <span>用户密码</span><input type="password"  id="password"/> <br>
    <span>验证码</span><input type="text"  id="kaptchaCode"/> <br>
    <img src="/kaptcha" id="kaptcha" width="110px" height="40px"><br>
    <input type="button" onclick="login()" value="登陆">
    <label><input type="checkbox" name="remember-me" id="remember-me"/>记住密码</label>
    <a href="/logout">退出</a>
</form>
<script text="text/javascript">
    window.onload = function (ev) {
        let kaptchaCode = document.getElementById("kaptcha");
        kaptchaCode.onclick = function (e) {
            console.log("onclick");
            kaptchaCode.src = "/kaptcha?" + Math.floor(Math.random()*100);
        }
    }
    function login() {
        let username = $("#username").val();
        let password = $("#password").val();
        let rememberMe = $("#remember-me").is(":checked");
        if(username === "" || password === "") {
            alert("用户名或密码不能为空");
            return;
        }
        $.ajax({
            type: "POST",
            url: "/login",
            data: {
                "username": username,
                "password": password,
                // 目前名字一定要是 remember-me
                "remember-me": rememberMe
            },
            success: function (json) {
                if (json.isok) {
                    location.href = '/';
                } else {
                    alert(json.message);
                    location.href = 'login.html';
                }
            },
            error: function (e){
                alert("发生了错误");
            }
        });
    }
</script>

验证码工具类

public class KaptchaImageVO {
    private String code;//验证码
    private LocalDateTime expiredTime; //过期时间

    public KaptchaImageVO(String code, int expiredAfterSeconds) {
        this.code = code;
        this.expiredTime = LocalDateTime.now().plusSeconds(expiredAfterSeconds); //当前时间加上seconds
    }
    public boolean isExpired() {
        return LocalDateTime.now().isAfter(expiredTime);
    }
}

验证码 controller

@RestController
public class KaptchaController {
    @Resource
    private DefaultKaptcha kaptchaProducer;
    @RequestMapping(value = "/kaptcha", method = RequestMethod.GET)
    public void kaptcha(HttpSession session, HttpServletResponse response) throws IOException {
        // 默认的配置 固定写法
        response.setDateHeader("Expires", 0);
        response.setHeader("Cache-Control","no-store,no-cache, must-revalidate");
        response.addHeader("Cache-Control", "post-check=0, pre-check=0");
        response.setHeader("Pragma", "no-cache");
        response.setContentType("image/jpeg");

        String kapText = kaptchaProducer.createText(); // 生产谜底
        session.setAttribute("kaptcha_key", new KaptchaImageVO(kapText, 120));
        BufferedImage image = kaptchaProducer.createImage(kapText); // 生成验证码图片
        ServletOutputStream outputStream = response.getOutputStream(); // 获取流
        ImageIO.write(image, "jpg", outputStream);
        outputStream.flush();
    }

}
  1. 验证码校验

前端

<form action="/login" method="post">
    <span>用户名称</span><input type="text"  id="username"/> <br>
    <span>用户密码</span><input type="password"  id="password"/> <br>
    <span>验证码</span><input type="text"  id="kaptchaCode"/> <br>
    <img src="/kaptcha" id="kaptcha" width="110px" height="40px"><br>
    <input type="button" onclick="login()" value="登陆">
    <label><input type="checkbox" name="remember-me" id="remember-me"/>记住密码</label>
    <a href="/logout">退出</a>
</form>
<script text="text/javascript">
    window.onload = function (ev) {
        let kaptchaCode = document.getElementById("kaptcha");
        kaptchaCode.onclick = function (e) {
            console.log("onclick");
            kaptchaCode.src = "/kaptcha?" + Math.floor(Math.random()*100);
        }
    }
    function login() {
        let username = $("#username").val();
        let password = $("#password").val();
        let rememberMe = $("#remember-me").is(":checked");
        let kaptchaCode = $("#kaptchaCode").val();
        if(username === "" || password === "") {
            alert("用户名或密码不能为空");
            return;
        }
        $.ajax({
            type: "POST",
            url: "/login",
            data: {
                "username": username,
                "password": password,
                // 目前名字一定要是 remember-me
                "remember-me": rememberMe,
                "kaptchaCode": kaptchaCode
            },
            success: function (json) {
                if (json.isok) {
                    location.href = '/'; //直接重定向到首页
                } else {
                    alert(json.message);
                    location.href = 'login.html';
                }
            },
            error: function (e){
                alert("发生了错误");
            }
        });
    }
</script>

定义一些常量

public class KaptchaConst {
    public static final String KAPtCHA_SESSION_KEY = "kaptcha_key";
}
public class RequestMethodConst {
    public static final String GET = "GET";
    public static final String POST = "POST";
}

验证码过滤器

@Component
public class KaptchaCodeFilter extends OncePerRequestFilter {
    @Resource
    MyAuthenticationFailureHandler myAuthenticationFailureHandler;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        // 先验证请求的 uri 和 method
        if (StringUtils.equals(request.getRequestURI(), "/login") && StringUtils.equals(request.getMethod(), RequestMethodConst.POST)) {
            // 验证谜底 == 用户输入?
            try {
                validate(new ServletWebRequest(request));
            } catch (SessionAuthenticationException e) {
                myAuthenticationFailureHandler.onAuthenticationFailure(request, response, e);
                return;   // 失败后之间返回
            }
        }
        filterChain.doFilter(request, response);
    }

    /**
     * 校验验证码
     * @param request
     * @throws ServletRequestBindingException
     */
    private void validate(ServletWebRequest request) throws ServletRequestBindingException {
        HttpSession session = request.getRequest().getSession();
        String codeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(), "kaptchaCode");
        logger.info("codeInRequest = " + codeInRequest);
        if (StringUtils.isEmpty(codeInRequest)) {
            throw new SessionAuthenticationException("验证码不存在");
        }
        // 获取服务器session池中的验证码谜底
        KaptchaImageVO codeInSession = (KaptchaImageVO) session.getAttribute(KaptchaConst.KAPtCHA_SESSION_KEY);
        logger.info("codeInSession = " + codeInSession);
        if (Objects.isNull(codeInSession)) {
            throw new SessionAuthenticationException("验证码不存在");
        }
        // 校验服务器session池中的验证码是否过期
        if (codeInSession.isExpired()) {
            session.removeAttribute(KaptchaConst.KAPtCHA_SESSION_KEY);
            throw new SessionAuthenticationException("验证码过期");
        }
        // 骑牛验证码校验
        if (!StringUtils.equals(codeInSession.getCode(), codeInRequest)) {
            throw new SessionAuthenticationException("验证码不匹配");
        }
    }
}

springsecurity里的配置

// 将 kaptchaCodeFilter 放在 用户验证过滤器之前
http.addFilterBefore(kaptchaCodeFilter, UsernamePasswordAuthenticationFilter.class);

方式二

集群的方式:session存储到redis上,不存储到应用i上

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rwzlqwvj-1631712092577)(C:\Users\WadeHao\AppData\Roaming\Typora\typora-user-images\image-20210913091724757.png)]

方式三

无状态运用开发

对称算法:加密和解密都可以

在这里插入图片描述

短信验证码

在这里插入图片描述

在这里插入图片描述

获取

前端页面

<h1>短信登录</h1>
<form action="/smsLogin" method="get">
    <span>手机号</span><input type="text"  id="mobile"/> <br>
    <span>短信验证码</span><input type="password"  id="smsCode"/>
    <input type="button" onclick="getSmsCode()" value="获取"><br>
    <input type="button" onclick="smsLogin()" value="登陆">
</form>

<script text="text/javascript">
    function getSmsCode() {
        $.ajax({
            type: "get",
            url: "/smscode",
            data: {
                "mobile": $("#mobile").val()
            },
            success: function (json) {
                if(json.isok) {
                    alert(json.data);
                } else {
                    alert(json.message);
                }
            },
            error: function (e) {
                // 返回的不是json  就会执行 error()
                console.log(e.responseText);
            }
        });
    }
    function smsLogin() {
        $.ajax({
            type: "post",
            url: "/smsLogin",
            data: {
                "mobile": $("#mobile").val(),
                "smsCode": $("#smsCode").val()
            },
            success: function (json) {
                if (json.isok) {
                    location.href = '/'; //直接重定向到首页
                } else {
                    alert(json.message);
                    location.href = 'login.html';
                }
            },
            error: function (e) {
                alert("发生了错误");
            }
        });
    }
</script>

注意:前端的ajax的success和error方法根据响应状态码来触发。当XMLHttpRequest.status为200的时候,表示响应成功,此时触发success().其他状态码则触发error()。

controller

@Slf4j
@RestController
public class SmsController {
    @Resource
    private UserMapper mapper;

    // 成功发送验证码
    @GetMapping(value = "/smscode")
    public AjaxResponse sms(@RequestParam("mobile") String mobile, HttpSession session) throws JsonProcessingException {
        if (mobile == null) {
            log.error("mobile is null");
            return AjaxResponse.error(CustomExceptionType.USER_REENTER, "请输入您的手机号");
        }
        // 先从数据库查询是否有这个手机号
        User user = mapper.findUserByUsernameOrMobile(mobile);
        if (user == null) {
            log.error("输入的手机号未注册:" + mobile);
            return AjaxResponse.error(CustomExceptionType.USER_REENTER, "你的手机号未注册");
        }
        SmsCode smsCode = new SmsCode(RandomStringUtils.randomNumeric(4), 60, mobile);
        // 调用短信服务商的接口发送短信
        log.info(smsCode.getCode() + "+>" + mobile);
        session.setAttribute(CodeConst.SMS_SESSION_KEY, smsCode);
        return AjaxResponse.success("短信验证码已经发送");
    }
}

一些常量

public class CodeConst {
    public static final String KAPtCHA_SESSION_KEY = "kaptcha_key";
    public static final String SMS_SESSION_KEY = "sms_key";
}

短信验证码类

@Getter
@Setter
public class SmsCode {
    private String code;//验证码
    private LocalDateTime expiredTime; //过期时间
    private String mobile; // 发送的手机号

    public SmsCode(String code, int expiredAfterSeconds, String mobile) {
        this.code = code;
        this.expiredTime = LocalDateTime.now().plusSeconds(expiredAfterSeconds); //当前时间加上seconds
        this.mobile = mobile;
    }
    public boolean isExpired() {
        return LocalDateTime.now().isAfter(expiredTime);
    }
}

验证

在这里插入图片描述

短信验证码过滤器

@Component
public class SmsCodeValidateFilter extends OncePerRequestFilter {
    @Resource
    private UserMapper mapper;
    @Resource
    private MyAuthenticationFailureHandler myAuthenticationFailureHandler;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        if (StringUtils.equals("/smsLogin", request.getRequestURI()) && StringUtils.equals("POST", request.getMethod())) {
            try {
                validate(new ServletWebRequest(request));
            } catch (AuthenticationException e) {
                myAuthenticationFailureHandler.onAuthenticationFailure(request, response, e);
            }
        }
        doFilter(request, response, filterChain);
    }
    /**
     * 校验规则
     * @param request
     * @throws ServletRequestBindingException
     */
    private void validate(ServletWebRequest request) throws ServletRequestBindingException {
        String mobileInReq = request.getParameter("mobile");
        if (Objects.isNull(mobileInReq)) {
            throw new SessionAuthenticationException("手机号不能为空");
        }
        String codeInReq = request.getParameter("smsCode");
        if(codeInReq == null) {
            throw new SessionAuthenticationException("短信验证码不能为空");
        }
        HttpSession session = request.getRequest().getSession();
        SmsCode codeInSession = (SmsCode) session.getAttribute(CodeConst.SMS_SESSION_KEY);
        if(Objects.isNull(codeInSession)) {
            throw new SessionAuthenticationException("验证码不存在");
        }
        if(codeInSession.isExpired()){
           session.removeAttribute(CodeConst.SMS_SESSION_KEY);
           throw new SessionAuthenticationException("验证码已经过期");
        }
        if(!codeInSession.equals(codeInReq)) {
            throw new SessionAuthenticationException("验证码不正确");
        }
        if(!mobileInReq.equals(codeInSession.getMobile())) {
            throw new SessionAuthenticationException("输入的手机号与发送短信目标手机号不匹配");
        }
        User user = mapper.findUserByUsernameOrMobile(mobileInReq);
        if(Objects.isNull(user)) {
            throw new SessionAuthenticationException("你输入的手机号不是系统注册的手机号");
        }
        session.removeAttribute(CodeConst.SMS_SESSION_KEY);
    }
}

登录

在这里插入图片描述

SpringSecurity没有提供短信验证码登录。

思路:对SpringSecurity 进行扩展,重写 UsernamePasswordAuthenticationFilter类 和 DaoAuthenticationProvider类,实现对应的功能。

SmsCodeAuthenticationFilter

public class SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    public static final String SPRING_SECURITY_FORM_MOBILE_KEY = "mobile";
    private String mobileParameter = SPRING_SECURITY_FORM_MOBILE_KEY;
    private boolean postOnly = true;

    public SmsCodeAuthenticationFilter() {
        super(new AntPathRequestMatcher("/smsLogin", "POST"));
    }

    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (this.postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        } else {
            String mobile = this.obtainMobile(request);
            if (mobile == null) {
                mobile = "";
            }
            mobile = mobile.trim();
            SmsCodeAuthenticationToken authRequest = new SmsCodeAuthenticationToken(mobile);
            this.setDetails(request, authRequest);
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }

    @Nullable
    protected String obtainMobile(HttpServletRequest request) {
        return request.getParameter(this.mobileParameter);
    }

    protected void setDetails(HttpServletRequest request, SmsCodeAuthenticationToken authRequest) {
        authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
    }

    public void setMobileParameter(String mobileParameter) {
        Assert.hasText(mobileParameter, "mobile parameter must not be empty or null");
        this.mobileParameter = mobileParameter;
    }

    public void setPostOnly(boolean postOnly) {
        this.postOnly = postOnly;
    }

    public final String getMobileParameter() {
        return this.mobileParameter;
    }

}

SmsCodeAuthenticationToken

public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {
    private static final long serialVersionUID = 520L;
    // 存放认证信息,认证之前放手机号,认证之后User
    private final Object principal;

    public SmsCodeAuthenticationToken(Object principal) {
        super((Collection)null);
        this.principal = principal;
        this.setAuthenticated(false);
    }

    public SmsCodeAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        super.setAuthenticated(true);
    }

    /**
     * 相当于是密码  但是不需要密码 值
     * @return
     */
    @Override
    public Object getCredentials() {
        return null;
    }

    public Object getPrincipal() {
        return this.principal;
    }

    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
        if (isAuthenticated) {
            throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
        } else {
            super.setAuthenticated(false);
        }
    }
    public void eraseCredentials() {
        super.eraseCredentials();
    }
}

SmsCodeAuthenticationProvider

@Getter
@Setter
@AllArgsConstructor
public class SmsCodeAuthenticationProvider implements AuthenticationProvider {

    private UserDetailsService userDetailsService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        //认证之前的Token 保存的是手机号
        SmsCodeAuthenticationToken authenticationToken = (SmsCodeAuthenticationToken) authentication;
        // 拿到用户信息
        UserDetails userDetails = userDetailsService.loadUserByUsername((String) authenticationToken.getPrincipal());
        if(userDetails == null) {
            throw new InternalAuthenticationServiceException("无法根据手机号获取用户信息");
        }
        //认证之后的Token 保存的是 UserDetails 和 权限信息
        SmsCodeAuthenticationToken authenticationResult = new SmsCodeAuthenticationToken(userDetails, userDetails.getAuthorities());
        authenticationResult.setDetails(authenticationToken.getDetails());
        return authenticationResult;
    }

    /**
     *  AuthenticationManager 维护的接口
     * @param aClass
     * @return
     */
    @Override
    public boolean supports(Class<?> aClass) {
        return SmsCodeAuthenticationToken.class.isAssignableFrom(aClass);
    }
}

将组件组装起来

在这里插入图片描述

由于配置复杂,可以写一个配置子类,然后在configure方法里面用http.apply(Class) 配置

@Component
public class SmsCodeSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
    @Resource
    MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
    @Resource
    MyAuthenticationFailureHandler myAuthenticationFailureHandler;
    @Autowired
    @Qualifier("UserServiceImpl")
    UserService userService;
    @Resource
    SmsCodeValidateFilter smsCodeValidateFilter;
    @Override
    public void configure(HttpSecurity http) throws Exception {
        SmsCodeAuthenticationFilter smsCodeAuthenticationFilter = new SmsCodeAuthenticationFilter();
        smsCodeAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
        smsCodeAuthenticationFilter.setAuthenticationSuccessHandler(myAuthenticationSuccessHandler);
        smsCodeAuthenticationFilter.setAuthenticationFailureHandler(myAuthenticationFailureHandler);

        SmsCodeAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeAuthenticationProvider(userService);

        // 配置filter的顺序 配置provider
        http.addFilterBefore(smsCodeValidateFilter, UsernamePasswordAuthenticationFilter.class);
        http.authenticationProvider(smsCodeAuthenticationProvider)
                .addFilterAfter(smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

    }
}
// 包含并生效配置类 SmsCodeSecurityConfig
http.apply(smsCodeSecurityConfig);

JWT使用场景 及 结构安全

场景

在这里插入图片描述

结构

JWT令牌:json web tokens.
在这里插入图片描述

具体时序图

两个流程:

  • 认证流程
  • 鉴别流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mk76YcNy-1631712092584)(C:\Users\WadeHao\AppData\Roaming\Typora\typora-user-images\image-20210915140804976.png)]

  1. 引入依赖

    <!--jwt-->
    <dependency>
       <groupId>io.jsonwebtoken</groupId>
       <artifactId>jjwt</artifactId>
       <version>0.9.0</version>
    </dependency>
    
  2. jwt工具类

    @Data
    @Component
    @ConfigurationProperties(prefix = "jwt")
    public class JwtTokenUtils {
    
        private String secret;
        private Long expiration;
        private String header;
    
        /**
         * 生成token
         *
         * @param userDetails
         * @return
         */
        public String generateToken(UserDetails userDetails) {
            Map<String, Object> claims = new HashMap<>(2);
            claims.put("sub", userDetails.getUsername());
            claims.put("created", new Date());
            return generateToken(claims);
        }
    
        /**
         * 从令牌中获取用户名,有了用户名就可以调用userDetailsServices验证
         *
         * @param token
         * @return
         */
        public String getUsernameFromToken(String token) {
            String username;
            try {
                Claims claims = getClaimsFromToken(token);
                username = claims.getSubject();
            } catch (Exception e) {
                username = null;
            }
            return username;
        }
    
        /**
         * 验证令牌:从数据库加载的token 与 用户传来的token 一致?
         *
         * @param token
         * @param userDetails
         * @return
         */
        public boolean validateToken(String token, UserDetails userDetails) {
            if (isTokenExpired(token)) return false;
            String username = getUsernameFromToken(token);
            return username.equalsIgnoreCase(userDetails.getUsername());
        }
    
        /**
         * 校验令牌是否过期
         *
         * @param token
         * @return
         */
        public boolean isTokenExpired(String token) {
            try {
                Claims claims = getClaimsFromToken(token);
                Date expiration = claims.getExpiration();
                return expiration.before(new Date());
            } catch (Exception e) {
                return false;
            }
        }
    
        /**
         * 刷新令牌
         *
         * @param token
         * @return
         */
        public String refreshToken(String token) {
            String refreshedToken;
            try {
                Claims claims = getClaimsFromToken(token);
                claims.put("created", new Date());
                refreshedToken = generateToken(claims);
            } catch (Exception e) {
                refreshedToken = null;
            }
            return refreshedToken;
        }
    
        /**
         * 从令牌中获取数据声明
         *
         * @param token
         * @return
         */
        private Claims getClaimsFromToken(String token) {
            Claims claims;
            try {
                claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
            } catch (Exception e) {
                claims = null;
            }
            return claims;
        }
    
        /**
         * @param claims
         * @return
         */
        private String generateToken(Map<String, Object> claims) {
            Date expirationDate = new Date(System.currentTimeMillis() + expiration);
            return Jwts.builder().setClaims(claims)
                    .setExpiration(expirationDate)
                    .signWith(SignatureAlgorithm.HS512, secret) //生成方法,加密方法
                    .compact();
        }
    }
    
  3. yml 配置

    jwt:
      secret: adsfdsgaadasdasdasd #实际开发中是从jvm参数中传进来
      expiration: 3600000
      header: JWTHeaderName
    
  4. service 层

    @Service
    public class JwtAuthService {
        @Resource
        JwtTokenUtils jwtTokenUtils;
        @Resource
        AuthenticationManager authenticationManager;
        @Resource
        UserDetailsService userDetailsService;
    
        /**
         * 登录认证换取 jwt token
         * @return
         */
        public String login(String username, String password) throws CustomException {
            try {
                UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken(username, password);
                // 认证
                Authentication authentication = authenticationManager.authenticate(upToken);
                SecurityContextHolder.getContext().setAuthentication(authentication);
            } catch (AuthenticationException e) {
                throw new CustomException(CustomExceptionType.USER_INPUT_ERROR, "用户名或者密码不正确");
            }
            // 加载用户信息
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
            // 生产 token
            return jwtTokenUtils.generateToken(userDetails);
        }
        /**
         * 刷新Token
         * @param oldToken
         * @return
         */
        public String refreshToken(String oldToken) {
            if (jwtTokenUtils.isTokenExpired(oldToken)) {
                return null;
            }
            return jwtTokenUtils.refreshToken(oldToken);
        }
    
    }
    
  5. controller层

    @RestController
    public class JwtAuthController {
        @Resource
        JwtAuthService jwtAuthService;
        @RequestMapping("/auth")
        public AjaxResponse login(@RequestBody Map<String, String> map) {
            String username = map.get("username");
            String password = map.get("password");
            if (password.isEmpty() || username.isEmpty()) {
                return AjaxResponse.error(CustomExceptionType.USER_INPUT_ERROR, "用户名或者密码不能为空");
            }
            try {
                return AjaxResponse.success(jwtAuthService.login(username, password));
            } catch (CustomException e) {
                return AjaxResponse.error(e);
            }
        }
        @RequestMapping("/refreshToken")
        public AjaxResponse refresh(@RequestHeader("${jwt.header}") String token) { // @RequestHeader从配置文件加载
            return AjaxResponse.success(jwtAuthService.refreshToken(token));
        }
    }
    
  6. 用postman工具验证

跨域访问问题

JWT集群

一个小项目

Spring Security中,基于表达式的权限控制,同样可以用在前台页面使用:

  • 表达式 描述
  • hasRole([role]) 当前用户是否拥有指定角色。
  • hasAnyRole([role1,role2]) 多个角色是一个以逗号进行分隔的字符串。如果当前用户拥有指定角色中的任意一个则返回true。
  • hasAuthority([auth]) 等同于hasRole
  • hasAnyAuthority([auth1,auth2]) 等同于hasAnyRole
  • Principle 代表当前用户的principle对象
  • authentication 直接从SecurityContext获取的当前Authentication对象
  • permitAll 总是返回true,表示允许所有的
  • denyAll 总是返回false,表示拒绝所有的
  • isAnonymous() 当前用户是否是一个匿名用户
  • isRememberMe() 表示当前用户是否是通过Remember-Me自动登录的
  • isAuthenticated() 表示当前用户是否已经登录认证成功了。
  • isFullyAuthenticated() 如果当前用户既不是一个匿名用户,同时又不是通过Remember-Me自动登录的,则返回true。
  • hasIpAddress(‘10.10.10.3’) ip地址的验证

作者:程就人生
链接:https://www.jianshu.com/p/953c8998e2bd
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
ller层

@RestController
public class JwtAuthController {
    @Resource
    JwtAuthService jwtAuthService;
    @RequestMapping("/auth")
    public AjaxResponse login(@RequestBody Map<String, String> map) {
        String username = map.get("username");
        String password = map.get("password");
        if (password.isEmpty() || username.isEmpty()) {
            return AjaxResponse.error(CustomExceptionType.USER_INPUT_ERROR, "用户名或者密码不能为空");
        }
        try {
            return AjaxResponse.success(jwtAuthService.login(username, password));
        } catch (CustomException e) {
            return AjaxResponse.error(e);
        }
    }
    @RequestMapping("/refreshToken")
    public AjaxResponse refresh(@RequestHeader("${jwt.header}") String token) { // @RequestHeader从配置文件加载
        return AjaxResponse.success(jwtAuthService.refreshToken(token));
    }
}
  1. 用postman工具验证

跨域访问问题

JWT集群

一个小项目

Spring Security中,基于表达式的权限控制,同样可以用在前台页面使用:

  • 表达式 描述
  • hasRole([role]) 当前用户是否拥有指定角色。
  • hasAnyRole([role1,role2]) 多个角色是一个以逗号进行分隔的字符串。如果当前用户拥有指定角色中的任意一个则返回true。
  • hasAuthority([auth]) 等同于hasRole
  • hasAnyAuthority([auth1,auth2]) 等同于hasAnyRole
  • Principle 代表当前用户的principle对象
  • authentication 直接从SecurityContext获取的当前Authentication对象
  • permitAll 总是返回true,表示允许所有的
  • denyAll 总是返回false,表示拒绝所有的
  • isAnonymous() 当前用户是否是一个匿名用户
  • isRememberMe() 表示当前用户是否是通过Remember-Me自动登录的
  • isAuthenticated() 表示当前用户是否已经登录认证成功了。
  • isFullyAuthenticated() 如果当前用户既不是一个匿名用户,同时又不是通过Remember-Me自动登录的,则返回true。
  • hasIpAddress(‘10.10.10.3’) ip地址的验证

作者:程就人生
链接:https://www.jianshu.com/p/953c8998e2bd
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值