java:spring-security的简单例子

【pom.xml】

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.3.12.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    <version>2.3.12.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>2.3.12.RELEASE</version>
</dependency>

【/resources/public/login.html】

<!DOCTYPE html>
<html>
    <head>
        <title>Spring Security Example </title>
    </head>
    <body>
        <form action="/security/login2" method="get">
            <div><label> User Name : <input type="text" name="username" value="user"/> </label></div>
            <div><label> Password: <input type="password" name="password" value="123"/> </label></div>
            <div><input type="submit" value="Sign In"/></div>
        </form>
    </body>
</html>

【SecurityConfig.java】

package com.chz.mySpringSecurity.config;

import com.chz.mySpringSecurity.filter.AccessTokenFilter;
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.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.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
    @Autowired
    private AccessTokenFilter accessTokenFilter;

    @Override
    protected void configure(HttpSecurity http) throws Exception
    {
        http.authorizeRequests()
                //访问"/"和"/home"路径的请求都允许
                .antMatchers(
                        "/",            // 这个会跳转到主页,要放过
                        "/home",           // 这是主页的地址,要放过
                        "/security/login2"          // 这个是提交登录的地址,要放过
                )
                .permitAll()
                //而其他的请求都需要认证
                .anyRequest()
                .authenticated()
                .and()
                //修改Spring Security默认的登陆界面
                .formLogin()
                .loginPage("/security/login")       // 登录地址是【/security/login】
                .permitAll()
                .and()
                .logout()
                .permitAll();

        http.addFilterBefore(accessTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();     // 这个会对用户提交的密码进行编码,然后才跟密码库里面的密码进行比较
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

【PublicController.java】

package com.chz.mySpringSecurity.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Slf4j
@Controller
@RequestMapping("/")
public class PublicController
{
    @GetMapping(value = {"/home","/"})
    @ResponseBody
    public String home(){
        log.info("chz >>> SecurityController.home(): ");
        return "this is home page!";
    }
 
}

【SecurityController.java】

package com.chz.mySpringSecurity.controller;

import com.chz.mySpringSecurity.entity.LoginUser;
import com.chz.mySpringSecurity.entity.LoginUsers;
import com.chz.mySpringSecurity.entity.ResponseResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Enumeration;
import java.util.Objects;
import java.util.concurrent.ThreadLocalRandom;

@Slf4j
@Controller
@RequestMapping("/security")
public class SecurityController 
{
    @Autowired
    private AuthenticationManager authenticationManager;

    @GetMapping(value = "/hello")
    @ResponseBody
    public String hello(){
        log.info("chz >>> SecurityController.hello(): ");
        return "this is hello page!";
    }

 
    @GetMapping(value = "/login")
    public String login(HttpServletRequest request, HttpServletResponse response){
        log.info("chz >>> SecurityController.login(): ");
        return "/login.html";
    }

    @GetMapping(value = "/login2")
    @ResponseBody
    public ResponseResult login2(@RequestParam String username, @RequestParam String password)
    {
        log.info("chz >>> SecurityController.login2(): username={}, password={}", username, password);

        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);      // 这个会触发【UserDetailsService.loadUserByUsername(String username)】方法被调用
        if(Objects.isNull(authenticate)){
            log.info("chz >>> SecurityController.login2(): 用户名或密码错误");
            throw new RuntimeException("用户名或密码错误");
        }

        LoginUser loginUser = (LoginUser)authenticate.getPrincipal();
        loginUser.setRefreshToken(Math.abs(ThreadLocalRandom.current().nextLong())+"");
        loginUser.setAccessToken(Math.abs(ThreadLocalRandom.current().nextLong())+"");
        LoginUsers.users.put(loginUser.getAccessToken(), loginUser);
        log.info("chz >>> accessToken: " + loginUser.getAccessToken());

        // context里面设置了【authenticationToken】就表示用户已经登录了,但是这个是根据cookie里面有sessionId判断的,跟accessToken无关
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);

        return new ResponseResult(200,"登陆成功", loginUser.getAccessToken());
    }

    @GetMapping(value = "/logout2")
    @ResponseBody
    public ResponseResult logout2(@RequestParam String accessToken)
    {
        log.info("chz >>> SecurityController.login2(): accessToken={}", accessToken);
        LoginUsers.users.remove(accessToken);

        // context里面清除了【authenticationToken】就表示用户已经退出登录了,但是这个是根据cookie里面有sessionId判断的,跟accessToken无关
        SecurityContextHolder.getContext().setAuthentication(null);

        return new ResponseResult(200,"退出成功");
    }
}

【LoginUser.java】

package com.chz.mySpringSecurity.entity;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.HashSet;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class LoginUser implements UserDetails
{
    private String accessToken;
    private String refreshToken;
    private HashSet<GrantedAuthority> authorities = new HashSet<>();

    private User user;

    public LoginUser(User user)
    {
        this.user = user;
    }

    public void addAuthority(String authority)
    {
        authorities.add(new SimpleGrantedAuthority(authority));
    }

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

 
    @Override
    public String getPassword() {
        return user.getPassword();
    }

 
    @Override
    public String getUsername() {
        return user.getUserName();
    }

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

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

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

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

【LoginUsers.java】

package com.chz.mySpringSecurity.entity;

import java.util.concurrent.ConcurrentHashMap;

public class LoginUsers
{
    // 这个map模拟的是分布式缓存redis的数据,代表已登录的用户列表
    public static ConcurrentHashMap<String, LoginUser> users = new ConcurrentHashMap<>();
}

【ResponseResult.java】

package com.chz.mySpringSecurity.entity;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class ResponseResult<T> {

    private Integer code;
    private String msg;
    private T data;

 
    public ResponseResult(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

 
    public ResponseResult(Integer code, T data) {
        this.code = code;
        this.data = data;
    }

 
    public ResponseResult(Integer code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }
}

【User.java】

package com.chz.mySpringSecurity.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable 
{
    private String userName;
    private String password;
}

【MyExceptionHandler.java】

package com.chz.mySpringSecurity.exceptions;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@Slf4j
@RestControllerAdvice
public class MyExceptionHandler 
{
    @ExceptionHandler(Exception.class)
    public String handleException(Exception e){
        log.error("chz >>> err", e);
        return "发生异常了";
    }
}

【AccessTokenFilter.java】

package com.chz.mySpringSecurity.filter;

import com.chz.mySpringSecurity.entity.LoginUser;
import com.chz.mySpringSecurity.entity.LoginUsers;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Slf4j
@Component
public class AccessTokenFilter extends OncePerRequestFilter
{
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException
    {
        log.info("chz >>> ChzAuthenticationTokenFilter.doFilterInternal(): uri:{}, queryParam={}", request.getRequestURI(), request.getQueryString());
        LoginUser loginUser = null;
        String accessToken = request.getParameter("accessToken");
        if( !StringUtils.isEmpty(accessToken) ) {
            loginUser = StringUtils.isEmpty(accessToken) ? null : LoginUsers.users.get(accessToken);
        }

        if( loginUser!=null ){
            // 这是登录过的,可以访问受限资源
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, loginUser.getPassword(), loginUser.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        }else {
            // 没有accessToken清空掉authentication,不让访问受限资源
            SecurityContextHolder.getContext().setAuthentication(null);
        }
        // 不管有没有登录,后面的【UsernamePasswordAuthenticationFilter】会进行权限判断,也直接放过
        filterChain.doFilter(request, response);
    }
}

【UserRepository.java】

package com.chz.mySpringSecurity.repository;

import com.chz.mySpringSecurity.entity.User;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import java.util.concurrent.ConcurrentHashMap;

public class UserRepository 
{
    // 这个用于模拟数据库里面的可登录用户的信息
    public static ConcurrentHashMap<String, User> users = new ConcurrentHashMap<>();

    static {
        //
        User user = new User();
        user.setUserName("user");
        user.setPassword(new BCryptPasswordEncoder().encode("123"));
        users.put(user.getUserName(), user);
        //
        User admin = new User();
        admin.setUserName("admin");
        admin.setPassword(new BCryptPasswordEncoder().encode("456"));
        users.put(admin.getUserName(), admin);
    }
}

【UserDetailsServiceImpl.java】

package com.chz.mySpringSecurity.service;

import com.chz.mySpringSecurity.entity.LoginUser;
import com.chz.mySpringSecurity.entity.User;
import com.chz.mySpringSecurity.repository.UserRepository;
import lombok.extern.slf4j.Slf4j;
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;

@Slf4j
@Service
public class UserDetailsServiceImpl implements UserDetailsService
{
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
    {
        log.info("chz >>> UserDetailsServiceImpl.loadUserByUsername(): username={}", username);
        User user = UserRepository.users.get(username);
        if( user==null ){
            throw new UsernameNotFoundException("用户名不存在");
        }

        LoginUser loginUser = new LoginUser(user);
        loginUser.addAuthority("chz_role1");
        return loginUser;
    }
}

【MySpringSecurityMain.java】

package com.chz.mySpringSecurity;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;

@EnableGlobalMethodSecurity(prePostEnabled = true)
@SpringBootApplication
public class MySpringSecurityMain
{
    public static void main(String[] args)
    {
        SpringApplication.run(MySpringSecurityMain.class, args);
    }
}

运行【MySpringSecurityMain】。

访问【http://localhost:8080/security/hello】 ,可以看到被重定向到登录页面【http://localhost:8080/security/login】
在这里插入图片描述

用户名输入【user】,密码输入【123】,点击【Sign In】登录。
在这里插入图片描述
可以得到一个【accessToken】=【8496842402128172477】
再次访问【http://localhost:8080/security/hello】,可以看到虽然已经登录成功了,但还是被重定向到了登录页面【http://localhost:8080/security/login】。
这是因为【url】里面没有带上【accessToken】,【AccessTokenFilter】自动将用户的登录信息清除了。
带上accessToken再试试试访问【http://localhost:8080/security/hello?accessToken=848234722712551727】
在这里插入图片描述
可以看到访问成功了。

  • 23
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

陈鸿圳

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

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

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

打赏作者

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

抵扣说明:

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

余额充值