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 Authentication、Spring LDAP)或OAuth(詳參:Spring Security OAuth)整合。
Spring Security (官方文件)
基本設定
可以在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裡。
@PostAuthorize Security Annotation Example (當伺服器需要先讀取資料才能判斷是否有讀取權限時)
首先,在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 Boot + React: JWT Authentication with Spring Security
spring security
Firebase
Google
登入
在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>
}
常見問題
想在spring中取得登入資料: Spring Security. Get Authenticated Principal Details.
如何處理CORS的問題
如何登出?
如何使用JWT?