Spring Security

基本概念

2019/08/03
2021/12/07 (更新內容)
2021/12/14 (更新內容)
2021/12/17 (更新內容)
2022/01/04 (增加連結)

Spring提供一個相當完整且簡單的登入驗證機制,可以將帳號密碼寫在程式中(in memory)的方式進行(詳參:In-Memory Authentication),也可以將帳號密碼儲存在資料庫(詳參:JDBC Authentication),也可以跟Lightweight Directory Access Protocol (LDAP)(詳參:LDAP AuthenticationSpring LDAP)或OAuth(詳參:Spring Security OAuth)整合。

基本設定

可以在pom.xml 中增加

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-security</artifactId>

</dependency>

或者利用vs code裡點選pom.xml,按右鍵,選擇「Add Starters...」,選擇「Spring Security」。

這時候,當我們啟動spring,Spring Security就會要求登入。預設的帳號是「user」,可以在啟動伺服器時,找到這一段,可以看到預設的密碼:

Using generated security password:

如果要自訂帳號、密碼、角色,可以直接修改application.properties,裡面應該已經有資料庫的設定,現在,可以加上Spring Security的設定

src/main/resources/application.properties

spring.security.user.name=manager

spring.security.user.password=password

spring.security.user.roles=manager

重新啟動,就會發現帳號、密碼已經修改了。

雖然,spring security可以利用application.properties做最基本的設定,但是,一般而言,帳號、密碼不會儲存在設定檔。後面,會說明如何利用資料表儲存帳號、密碼。

權限設定

最簡單的設定就是先產生一個SecurityConfig.java,設定一定登入,而且使用/customer必須要有manager的角色(role)。

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;


@EnableWebSecurity

public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override

protected void configure(HttpSecurity http) throws Exception {

http

.cors().and()

.csrf().disable().authorizeRequests()

.antMatchers("/customer").hasRole("manager")

.anyRequest().authenticated()

.and()

.formLogin();

}

}

假設,我們希望開放/,而只要管制/customer下的檔案,可以把SecurityConfig.java加上:

.antMatchers("/").permitAll()

重啟伺服器之後,會發現,只有進入/customer才會被要求登入。

完整內容:

package com.example.demo;


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;


@EnableWebSecurity

public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override

protected void configure(HttpSecurity http) throws Exception {

http

.cors().and()

.csrf().disable().authorizeRequests()

.antMatchers("/").permitAll()

.antMatchers("/customer").hasRole("manager")

.anyRequest().authenticated()

.and()

.formLogin();

}

}

那如果,我們要限制post才要登入呢?

package com.example.demo;


import org.springframework.http.HttpMethod;


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;


@EnableWebSecurity

public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override

protected void configure(HttpSecurity http) throws Exception {

http

.cors().and()

.csrf().disable().authorizeRequests()

.antMatchers("/").permitAll()

.antMatchers(HttpMethod.GET, "/customer").permitAll()

.antMatchers(HttpMethod.POST,"/customer").hasRole("manager")

.anyRequest().authenticated()

.and()

.formLogin();

}

}

另外,還要先把cors()關閉,否則,spring會阻擋不是從8080發出的要求。並且要設定接受httpBasic的驗證,否則,spring是不接受遠端驗證的

package com.example.demo;


import org.springframework.http.HttpMethod;


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;


@EnableWebSecurity

public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override

protected void configure(HttpSecurity http) throws Exception {

http

.httpBasic().and()

//.cors().and()

.csrf().disable().authorizeRequests()

.antMatchers("/").permitAll()

.antMatchers(HttpMethod.GET, "/customer").permitAll()

.antMatchers(HttpMethod.POST,"/customer").hasRole("manager")

.anyRequest().authenticated()

.and()

.formLogin();

}

}

這時候,如果利用postman,就會收到登入的html,如果想要執行post,就可以在authorization下,選擇「Basic Auth」,並且輸入帳號、密碼。這樣就可以順利地透過postman呼叫post了。

接下來,把put及delete也加進去:

package com.example.demo;


import org.springframework.http.HttpMethod;


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;


@EnableWebSecurity

public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override

protected void configure(HttpSecurity http) throws Exception {

//http

http.httpBasic().and()

//.cors().and()

.csrf().disable().authorizeRequests()

.antMatchers("/").permitAll()

.antMatchers(HttpMethod.GET, "/customer").permitAll()

.antMatchers(HttpMethod.POST,"/customer").hasRole("manager")

.antMatchers(HttpMethod.PUT,"/customer").hasRole("manager")

.antMatchers(HttpMethod.DELETE,"/customer").hasRole("manager")

.anyRequest().authenticated()

.and()

.formLogin();

}

}

React

透過react的程式新增資料,就會被阻止,並在console可以看到:

Access to XMLHttpRequest at 'http://localhost:8080/login' (redirected from 'http://localhost:3000/customer') from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

跟跟postman一樣,要設定帳號、密碼。

import React, {useEffect, useState} from 'react';

import { Button, Dialog, DialogActions, DialogContent, TextField } from '@mui/material';

import axios from 'axios';

export default function CustomerAddEdit(props) {

console.log(props.customer);

const [customer, setCustomer] = useState(props.customer);

const handleClick = function(e){

setCustomer({...customer,[e.target.name]:e.target.value})

}


const update = async function(){

let auth = {auth: {

username: 'manager',

password: 'password'

}}


try{

if (customer.id){

await axios.put("/customer",customer, auth);

}

else {

await axios.post("/customer",customer, auth);

}


}

catch(e){

console.log(e);

}

props.close();

}


useEffect(()=>setCustomer(props.customer),[props.customer]);


return (

<Dialog open={props.open}>

<DialogContent>

<TextField label ="名稱" name ="name" variant="outlined" value={customer.name} onChange={handleClick}/>

<TextField label ="體重" type="number" name ="weight" variant="outlined" value={customer.weight} onChange={handleClick}/>

</DialogContent>

<DialogActions>

<Button variant="contained" color="primary" onClick={update}>{customer.id?"修改":"新增"}</Button>

<Button variant="contained" color="secondary" onClick={()=>props.close()}>取消</Button>

</DialogActions>

</Dialog>

);

}

刪除的部分,也要修改。

const deleteData = async function(id) {

let auth = {auth: {

username: 'manager',

password: 'password'

}}

await axios.delete("/customer/"+id, auth);

setDeleted(currentDeleted=>setDeleted(!currentDeleted));

}

不過,這樣的寫法是將帳號密碼寫死,最好的寫法,是讓使用者輸入帳號、密碼,將帳號、密碼儲存起來,當每次需要驗證時,利用帳號、密碼進行驗證。

@PreAuthorize

前面的寫法是將所有的權限管控都寫在SecurityConfig.java,統一管理。在spring security 4,提供了@PreAuthorize,可以將權限管控寫在controller裡。

首先,在SecurityConfig.java增加

@EnableGlobalMethodSecurity(prePostEnabled = true)

這樣的話,才會到controller裡讀取@preAuthorize。

package com.example.demo;

import org.springframework.http.HttpMethod;

import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;

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;


@EnableWebSecurity

@EnableGlobalMethodSecurity(prePostEnabled = true)

public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override

protected void configure(HttpSecurity http) throws Exception {

//http

http.httpBasic().and()

//.cors().and()

.csrf().disable().authorizeRequests()

.antMatchers("/").permitAll()

.anyRequest().authenticated()

.and()

.formLogin();

}

}

可以看到我們把權限設定都拿掉了。我們來修改controller裡的設定:

package com.example.demo.controller;


import java.sql.SQLException;

import java.util.List;


import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.security.access.prepost.PreAuthorize;

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

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.PathVariable;

import org.springframework.web.bind.annotation.PostMapping;

import org.springframework.web.bind.annotation.PutMapping;

import org.springframework.web.bind.annotation.DeleteMapping;

import org.springframework.web.bind.annotation.RequestBody;


import com.example.demo.dao.CustomerDAO;

import com.example.demo.entity.Customer;


@RestController


public class CustomerController {

@Autowired

CustomerDAO dao;

@GetMapping(value = {"/"})

public String test() throws SQLException{

return "OK";

}


@GetMapping(value = {"/customer"})

public List<Customer> retrieveCustomers() throws SQLException{

return dao.findAll();

}


@GetMapping(value = {"/customer/{id}"})

public Customer retrieveOneCustomer(@PathVariable("id") Long id) throws SQLException{

return dao.findOne(id);

}


@PreAuthorize("hasRole('manager')")

@PostMapping(value = "/customer")

public void processFormCreate(@RequestBody Customer customer) throws SQLException {

dao.insert(customer);

}


@PreAuthorize("hasRole('manager')")

@PutMapping(value = "/customer")

public void processFormUpdate(@RequestBody Customer customer) throws SQLException {

dao.update(customer);

}


@PreAuthorize("hasRole('manager')")

@DeleteMapping(value = "/customer/{id}")

public void deleteCustomer(@PathVariable("id") Long id) {

dao.delete(id);

}


}

Security in Memory

一般而言,是不會利用application.properties來設定帳號,在spring security,可以透過很簡單的設定,就可以進行帳號設定。最簡單的是透過in memory的方式。一般而言,我們會把密碼加密,可以透過: https://bcrypt-generator.com/ ,產生加密後的密碼。為了測試新設定是否成功,故意設定不同的角色:ADMIN、USER。(詳參: Spring Boot Security Basic Authentication – Secure REST API)

@Autowired

public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {

auth.inMemoryAuthentication()

//.withUser("admin").password("{noop}password")

.withUser("user").password("{bcrypt}$2a$10$51o3TO6lpenvo6WtO8P7uOXsGgfOaJDzkGuOIAPPB9usXpVVTAVf2")

.roles("USER").and()

.withUser("ben").password("{bcrypt}$2a$10$51o3TO6lpenvo6WtO8P7uOXsGgfOaJDzkGuOIAPPB9usXpVVTAVf2")

.roles("USER").and()

.withUser("admin").password("{bcrypt}$2a$10$51o3TO6lpenvo6WtO8P7uOXsGgfOaJDzkGuOIAPPB9usXpVVTAVf2")

.roles("ADMIN");

}

controller的權限也要一併修改

package com.example.demo.controller;


import java.sql.SQLException;

import java.util.List;


import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.security.access.prepost.PreAuthorize;

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

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.PathVariable;

import org.springframework.web.bind.annotation.PostMapping;

import org.springframework.web.bind.annotation.PutMapping;

import org.springframework.web.bind.annotation.DeleteMapping;

import org.springframework.web.bind.annotation.RequestBody;


import com.example.demo.dao.CustomerDAO;

import com.example.demo.entity.Customer;


@RestController


public class CustomerController {

@Autowired

CustomerDAO dao;

@PreAuthorize("hasAnyRole('ADMIN', 'USER')")

@GetMapping(value = {"/"})

public String test() throws SQLException{

return "OK";

}

@PreAuthorize("hasAnyRole('ADMIN', 'USER')")

@GetMapping(value = {"/customer"})

public List<Customer> retrieveCustomers() throws SQLException{

return dao.findAll();

}


@PreAuthorize("hasAnyRole('ADMIN', 'USER')")

@GetMapping(value = {"/customer/{id}"})

public Customer retrieveOneCustomer(@PathVariable("id") Long id) throws SQLException{

return dao.findOne(id);

}


@PreAuthorize("hasRole('ADMIN')")

@PostMapping(value = "/customer")

public void processFormCreate(@RequestBody Customer customer) throws SQLException {

dao.insert(customer);

}


@PreAuthorize("hasRole('ADMIN')")

@PutMapping(value = "/customer")

public void processFormUpdate(@RequestBody Customer customer) throws SQLException {

dao.update(customer);

}


@PreAuthorize("hasRole('ADMIN')")

@DeleteMapping(value = "/customer/{id}")

public void deleteCustomer(@PathVariable("id") Long id) {

dao.delete(id);

}


}

react

因為帳號及檢查規則改變了,這邊也要跟著修改

useEffect(() => {

async function fetchData () {

let auth = {auth: {

username: 'admin',

password: 'password'

}}

const result = await axios.get("/customer", auth);

console.log(result);

setCustomers(result.data);

}

fetchData();

},[open, deleted]);


const deleteData = async function(id) {

let auth = {auth: {

username: 'admin',

password: 'password'

}}

await axios.delete("/customer/"+id, auth);

setDeleted(currentDeleted=>setDeleted(!currentDeleted));

}

新增、修改的部分,也要跟著修正:

const update = async function(){

let auth = {auth: {

username: 'admin',

password: 'password'

}}


try{

if (customer.id){

await axios.put("/customer",customer, auth);

}

else {

await axios.post("/customer",customer, auth);

}


}

catch(e){

console.log(e);

}

props.close();

}


Security with JDBC

一般而言,是不會利用application.properties或in memory來設定帳號,在spring security,可以透過很簡單的設定,就可以進行帳號設定。首先,先在資料庫設定兩個資料表: (開了兩個帳號:admin、user,並設定admin為ROLE_ADMIN及ROLE_USER,設定user為ROLE_ADMIN及ROLE_USER,詳參: Security Database Schema

create table users(

username varchar(50) not null primary key,

password varchar(70) not null,

enabled boolean not null

);


create table authorities (

username varchar(50) not null,

authority varchar(50) not null,

constraint fk_authorities_users foreign key(username) references users(username)

);

create unique index ix_auth_username on authorities (username,authority);


INSERT INTO `users` VALUES ('admin','{bcrypt}$2a$10$UFHgwBdwzDl0Pdatt7EvQuMhtwWJUGuooPthfiK5u.PvOxh1H6nlW',1),('user','{bcrypt}$2a$10$UFHgwBdwzDl0Pdatt7EvQuMhtwWJUGuooPthfiK5u.PvOxh1H6nlW',1);

INSERT INTO `authorities` VALUES ('admin','ROLE_ADMIN'),('admin','ROLE_USER'),('user','ROLE_USER');

在configureGlobal()中利用jdbcAuthentication(),並設定dataSource。

package com.example.demo;


import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.http.HttpMethod;

import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;

import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;

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.factory.PasswordEncoderFactories;

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


import javax.sql.DataSource;


@EnableWebSecurity

@EnableGlobalMethodSecurity(prePostEnabled = true)

public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override

protected void configure(HttpSecurity http) throws Exception {

//http

http.httpBasic().and()

//.cors().and()

.csrf().disable().authorizeRequests()

.antMatchers("/").permitAll()

.antMatchers(HttpMethod.GET, "/customer").permitAll()

.anyRequest().authenticated()

.and()

.formLogin();

}

@Autowired

private DataSource dataSource;

@Autowired

public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {

PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();

auth

.jdbcAuthentication()

.dataSource(dataSource).passwordEncoder(passwordEncoder);

}

}

利用前面寫好的react介面,當使用到這個API時,網頁也會自動要求帳號、密碼,不需要額外的更動。

如果要用既有的資料表,可以利用: (Authenticating Spring Security 5 users with JDBC and MySQL )

@EnableWebSecurity

public class JdbcSecurityConfiguration extends WebSecurityConfigurerAdapter {

...


protected void configure(AuthenticationManagerBuilder auth) throws Exception {

PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();

auth

.jdbcAuthentication()

.dataSource(dataSource)

.passwordEncoder(passwordEncoder)

.usersByUsernameQuery(

"SELECT username, password, enabled from users where username = ?")

.authoritiesByUsernameQuery(

"SELECT u.username, a.authority " +

"FROM user_authorities a, users u " +

"WHERE u.username = ? " +

"AND u.id = a.user_id"

);

}

...

}


登入

在spring端,我們增加了一個AccountController.java,因為Spring Security會去檢查是否有登入的權限,不必在controller裡檢查帳號密碼。

package com.example.demo.controller;


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

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.PostMapping;


import org.springframework.security.access.prepost.PreAuthorize;


@RestController

public class AccountController {


@PreAuthorize("hasAnyRole('ADMIN', 'USER')")

@GetMapping(value = {"/account"})

public String login() {

return "登入成功";

}

}

在react端,不再寫死帳號密碼,而是使用使用者輸入的帳號密碼

import React, {useState} from 'react';

import {Button, TextField} from '@mui/material';

import axios from 'axios';


export default function SignIn() {

const [account, setAccount] = useState({username:"",password:"", displayName:""});

const [message, setMessage] = useState("");

const handleChange = function(e){

setAccount({...account,[e.target.name]:e.target.value})

}

const handleSubmit = async function(){

setMessage("");

let auth = {auth: account};

try {

const res = await axios.get("/account", auth);

console.log(auth);

if (res) {

console.log(res);

console.log(res.statusText);

setMessage(res.data);

}


}

catch(error){

console.log(error);

setMessage("無法登入");


}

}

return(

<form>

<TextField type = "text" name = "username" value={account.username}

placeholder="帳號" label="帳號:" onChange={handleChange} autoComplete="username"/><br/>

<TextField type = "password" name = "password" value={account.password}

placeholder="密碼" label="密碼:" onChange={handleChange} autoComplete="current-password"/><br/>

{message}<br/>

<Button variant="contained" color="primary" onClick={handleSubmit}>登入</Button>

<Button variant="contained" color="secondary">我要註冊</Button>

</form>

)

}

註冊

在spring端,在AccountController裡新增signUp:

package com.example.demo.controller;


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

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.PostMapping;

import org.springframework.web.bind.annotation.RequestBody;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.security.access.prepost.PreAuthorize;


import com.example.demo.dao.AccountDAO;

import com.example.demo.entity.Account;


@RestController

public class AccountController {

@Autowired

AccountDAO dao;

@PostMapping(value = {"/account"})

public String signUp(@RequestBody Account account) {

int result = dao.signUp(account);

return (result == 1)? "帳號已註冊成功": "帳號未註冊成功";

}

@PreAuthorize("hasAnyRole('ADMIN', 'USER')")

@GetMapping(value = {"/account"})

public String login() {

return "登入成功";

}

}

記得要讓這個方法可以不用身分驗證:

package com.example.demo;


import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.http.HttpMethod;

import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;

import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;

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.factory.PasswordEncoderFactories;

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


import javax.sql.DataSource;


@EnableWebSecurity

@EnableGlobalMethodSecurity(prePostEnabled = true)

public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override

protected void configure(HttpSecurity http) throws Exception {

//http

http.httpBasic().and()

//.cors().and()

.csrf().disable().authorizeRequests()

.antMatchers(HttpMethod.POST,"/account").permitAll() //overwrite rule in Controller

.antMatchers(HttpMethod.GET, "/customer").permitAll()

.anyRequest().authenticated()

.and()

.formLogin();

}


@Autowired

private DataSource dataSource;

@Autowired

public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {

PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();

auth

.jdbcAuthentication()

.dataSource(dataSource).passwordEncoder(passwordEncoder);

}

}

因為我們透過資料庫管理帳號,所以,我們需要AccountDAO.java:

package com.example.demo.dao;

import com.example.demo.entity.Account;


public interface AccountDAO {

public int signUp(Account account);

}

對應的AccountDAOImpl.java

package com.example.demo.dao;

import java.sql.Connection;

import java.sql.PreparedStatement;

import javax.sql.DataSource;


import com.example.demo.entity.Account;


import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Repository;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;



@Repository

public class AccountDAOImpl implements AccountDAO {

@Autowired

private DataSource dataSource;

public int signUp(Account account){

int result = 0;

try {

Connection conn = dataSource.getConnection();

String sql = "insert into users (username, password, enabled) values(?, ?, 1)";

PreparedStatement stmt = conn.prepareStatement(sql);

stmt.setString(1, account.getUsername());

String encoded = "{bcrypt}"+new BCryptPasswordEncoder().encode(account.getPassword());

stmt.setString(2, encoded);

result = stmt.executeUpdate();

if (result == 1){

sql = "insert into authorities (username, authority) values(?, 'ROLE_USER')";

stmt = conn.prepareStatement(sql);

stmt.setString(1, account.getUsername());

result = stmt.executeUpdate();

}

conn.close();

} catch(Exception e) {

//something wrong

System.out.println(e);

}

return result;

}

}

要記得密碼是需要加密:

String encoded = "{bcrypt}"+new BCryptPasswordEncoder().encode(account.getPassword());

stmt.setString(2, encoded);

對應的Account.java

package com.example.demo.entity;


public class Account {

private String username;

private String password;

private String role;


public Account(String username, String password, String role) {

this.username = username;

this.password = password;

this.role = role;

}


public String getUsername() {

return this.username;

}


public void setUsername(String username) {

this.username = username;

}


public String getPassword() {

return this.password;

}


public void setPassword(String password) {

this.password = password;

}


public String getRole() {

return this.role;

}


public void setRole(String role) {

this.role = role;

}


}

這樣就可以註冊、註冊後就可以登入了!

Spring Security with React Context

接下來,請先參考React Context

  • 請新增AuthContext.js,利用createContext產生Context

  • 新增AppRouter.js,將Router從index.js中移到AppRouter.js

  • 在AppRouter.js中設定AuthContext.Provider

在Main.js裡採用useContext

import React, {useContext} from 'react';


import { Box, Button } from '@mui/material';

import { useNavigate } from "react-router-dom";


import AppMenu from './AppMenu';

//import ImageUpload from './ImageUpload';

import {AuthContext, STATUS} from '../account/AuthContext';

import SignIn from '../account/SignIn';

import SignUp from '../account/SignUp';


export default function Main() {

const authContext = useContext(AuthContext);

const navigate = useNavigate();

const handleClick = function (link) {

navigate(link);

}

return (

<Box>

<AppMenu/>

<Button variant="contained" color="primary" onClick={()=>handleClick("/product")}>Product</Button>

<Button variant="contained" color="secondary" onClick={()=>handleClick("/customer")}>Customer</Button>

{(authContext.status===STATUS.toSignIn)?

<SignIn/>:

<SignUp/>

}

</Box>

)


}

就會只看到登入的畫面了!

要看到註冊畫面的話,把狀態改為「toSignUp」

<AuthContext.Provider value={{status:STATUS.toSignUp}}>

<Router>

<Routes>

<Route path="/" element={<Main/>}/>

<Route path="product" element={<ProductList/>}/>

<Route path="customer" element={<CustomerList/>}/>

</Routes>

</Router>

</AuthContext.Provider>

  • 接下來,我們在AuthContext.js新增setStatus

  • 在AppRouter.js裡,新增status,將status及更動的方法綁定AuthContext的Provider。

  • 在SignIn.js裡,利用useContext取得authContext,並透過setStatus更動狀態為「toSignUp」

只要登入過,spring security會知道已經登入,可以把前面預設的帳號密碼都移除。

接下來,請完成SignUp.js及相關的介面。

例如,登入成功之後,就把狀態改為「toSignOut」,如果沒有登入,利用以下範例,就可以在ProductList中把新增的按鈕隱藏起來:

{(authContext.status===STATUS.toSignOut) &&

<Fab color="primary" aria-label="新增" onClick={addData}

sx={{

position: "fixed",

bottom: (theme) => theme.spacing(2),

right: (theme) => theme.spacing(8)

}}

>

<AddIcon />

</Fab>

}

常見問題