SpringBoot整合SpringSecurity(一)入門程序

序言

Spring Security是一個功能強大且可高度自定義的身份驗證和訪問控制框架。它是保護基於Spring的應用程序的事實標準。

Spring Security是一個專注於爲Java應用程序提供身份驗證和授權的框架。與所有Spring項目一樣,Spring Security的真正強大之處在於它可以輕鬆擴展以滿足自定義要求。

這是Spring Security 官方的說明,早就聽聞 Spring Security 功能強大但上手困難,於是上手學習了學習,在此整理出幾篇文章,百度的坑是真的多。

代碼請參考 https://github.com/AutismSuperman/springsecurity-example

首先依賴

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

準備頁面

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

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

然後是controller

@Controller
@RequestMapping
public class TestController {


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

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

準備測試用戶

這裏呢我們就不連數據庫了,創造一些模擬的數據

首先呢是用戶的實體類

@Data
public class SysUser  {

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

首先呢是接口

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

然後實現類

@Service
public class UserServiceImpl implements IUserService {

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


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

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

配置SpringSecurity

找到誰

要想通過Security的用戶認證的話 必須要實現一個UserDetailsService的接口

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

}

這裏呢要實現一個返回 UserDetails 類的方法 ,

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

    String getPassword();

    String getUsername();

    boolean isAccountNonExpired();

    boolean isAccountNonLocked();

    boolean isCredentialsNonExpired();

    boolean isEnabled();
}

裏面與有各種的用戶狀態 這裏呢

security默認有一個org.springframework.security.core.userdetails.User的實現 我們返回的時候構建這個類即可,它有三個參數,分別是用戶名、密碼和權限集。

這一驟只是爲了讓security找到你是誰。

登陸成功怎麼辦

security提供了一個AuthenticationSuccessHandler 的接口默認實現是跳轉url,因爲程序走ajax了所以我們返回json

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

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

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

登陸失敗怎麼辦

與成功處理器對應的還有一個失敗處理器

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

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

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

WebSecurityConfig

上面做的那麼多但是並沒有跟security關聯上

這時候我們要繼承WebSecurityConfigurerAdapter 這個類來實現security的個性化配置

把我們自定義的 userDetailsServiceSuccessAuthenticationHandlerFailureAuthenticationHandler注入進來

這裏我們還指定了密碼的加密方式(5.0 版本強制要求設置),因爲我們構造的數據是明文的,所以明文返回即可,如下所示:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

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


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

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

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

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //http.httpBasic()  //httpBasic 登錄
        http.formLogin()
                .loginPage("/login")// 登陸的url
                .loginProcessingUrl("/authentication/form") // 自定義登錄路徑
                .failureHandler(failureAuthenticationHandler) // 自定義登錄失敗處理
                .successHandler(successAuthenticationHandler) // 自定義登錄成功處理
                .and()
                .logout()
                .logoutUrl("/logout")
                .and()
                .authorizeRequests()// 對請求授權
                 // 這些頁面不需要身份認證,其他請求需要認證
                .antMatchers("/login", "/authentication/require",
                        "/authentication/form").permitAll()
                .anyRequest() // 任何請求
                .authenticated() // 都需要身份認證
                .and()
                .csrf().disable();// 禁用跨站攻擊
    }
    @Override
    public void configure(WebSecurity web) throws Exception {
        // 設置攔截忽略文件夾,可以對靜態資源放行
        web.ignoring().antMatchers("/css/**", "/js/**");
    }

}

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

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

然後啓動項目會進入登陸頁面,輸入正確的用戶名和密碼即可。

本博文是基於springboot2.x 和security 5 如果有什麼不對的請在下方留言。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章