Turvalisus on mugavuse vaenlane ja vastupidi. See väide peab paika iga virtuaalse või reaalse süsteemi puhul, alates maja füüsilisest sissepääsust kuni veebipanga platvormideni. Insenerid üritavad pidevalt leida antud kasutuse jaoks sobiva tasakaalu, kaldudes ühele või teisele poole. Tavaliselt liigume uue ohu ilmnemisel turvalisuse poole ja eemale mugavusest. Seejärel näeme, kas suudame kaotatud mugavuse taastada ilma turvalisust liiga palju vähendamata. Pealegi kestab see nõiaring igavesti.
Proovime uurida REST-i turvalisuse olukorda tänapäeval, kasutades selleks lihtsat kevade turvaõpetust, et seda tegevuses näidata.
Teenused REST (mis tähistab esindusriikide ülekandmist) said alguse veebiteenuste äärmiselt lihtsustatud lähenemisviisist, millel olid tohutud spetsifikatsioonid ja tülikad vormingud, näiteks WSDL teenuse kirjeldamiseks või SEEP sõnumi vormingu täpsustamiseks. RESTis pole meil ühtegi neist. Võime kirjeldada REST-teenust lihttekstifailina ja kasutada uuesti mis tahes soovitud sõnumivormingut, näiteks JSON, XML või isegi lihttekst. Lihtsustatud lähenemisviisi rakendati ka REST-teenuste turvalisusele; ükski määratletud standard ei kehtesta konkreetset viisi kasutajate autentimiseks.
Kuigi REST-teenustes pole palju täpsustatud, on oluline riiklik puudus. See tähendab, et server ei hoia ühtegi kliendi olekut, hea näide on sessioonid. Seega vastab server igale päringule nii, nagu oleks see klient esimene teinud. Kuid isegi praegu kasutavad paljud rakendused endiselt küpsiste põhist autentimist, mis on päritud veebisaidi standardsest arhitektuursest kujundusest. REST-i kodakondsuseta lähenemine muudab seansiküpsised turvalisuse seisukohast sobimatuks, kuid sellest hoolimata kasutatakse neid endiselt laialdaselt. Lisaks nõutava kodakondsusetuse ignoreerimisele oli oodatud turvalisuse kompromiss lihtsustatud lähenemisviis. Võrreldes veebiteenuste WS-Security standardiga on REST-teenuste loomine ja tarbimine palju lihtsam, seega käis mugavus läbi katuse. Kompromiss on üsna õhuke turvalisus; seansi kaaperdamine ja saididevaheliste taotluste võltsimine (XSRF) on kõige levinumad turvaküsimused.
Serverist klientide seanssidest vabanemiseks on aeg-ajalt kasutatud mõnda muud meetodit, näiteks Basic või Digest HTTP autentimine. Mõlemad kasutavad Authorization
päis kasutaja mandaatide edastamiseks, lisades mõne kodeeringu (HTTP Basic) või krüptimise (HTTP Digest). Muidugi olid neil samad vead, mis veebilehtedelt leiti: HTTP Basicut tuli kasutada HTTPS-i kaudu, kuna kasutajanimi ja parool saadetakse hõlpsasti pöörduva base64-kodeeringuna ning HTTP Digest sundis kasutama vananenud MD5-i räsimist, mis on osutunud ebaturvaliseks.
geštaltpsühholoogia põhiprintsiibid
Lõpuks kasutasid mõned rakendused klientide autentimiseks suvalisi märke. See valik näib olevat praegu parim. Kui see õigesti rakendatakse, lahendab see kõik HTTP Basicu, HTTP Digesti või seansiküpsiste turbeprobleemid, seda on lihtne kasutada ja see järgib kodakondsuseta mustrit.
Selliste meelevaldsete märkide puhul on standard siiski vähe. Igal teenusepakkujal oli oma idee, mida tokenisse lisada ja kuidas seda kodeerida või krüptida. Eri teenusepakkujate teenuste tarbimine nõudis täiendavat seadistamisaega, et lihtsalt kohaneda konkreetse kasutatud märgivorminguga. Teised meetodid seevastu (seansiküpsis, HTTP Basic ja HTTP Digest) on arendajatele hästi teada ja peaaegu kõik kõigi seadmete brauserid töötavad nendega kastist väljas. Raamistikud ja keeled on nende meetodite jaoks valmis, omades sisseehitatud funktsioone, et nendega sujuvalt hakkama saada.
JWT (lühendatud JSON-i veebimärgist) on puuduv standardimine märgendite kasutamiseks veebis üldiselt autentimiseks, mitte ainult REST-teenuste jaoks. Praegu on see mustandina RFC 7519 . See on vastupidav ja mahutab palju teavet, kuid on siiski hõlpsasti kasutatav, kuigi selle suurus on suhteliselt väike. Nagu kõiki muid märke, saab ka JWT-d kasutada autentitud kasutajate identiteedi edastamiseks identiteedipakkuja ja teenusepakkuja vahel (mis ei pruugi olla samad süsteemid). See võib kanda ka kõiki kasutaja nõudeid, näiteks autoriseerimisandmeid, nii et teenusepakkuja ei pea iga päringu kasutajarollide ja -õiguste kontrollimiseks minema andmebaasi ega välissüsteemidesse; et andmed eraldatakse märgist.
JWT-turvalisus on loodud toimima järgmiselt.
Authorization
päis ja dekrüpteerib selle, vajadusel kinnitab allkirja ja kui kõik on korras, eraldab kasutaja andmed ja õigused. Ainult nende andmete põhjal ja jällegi ilma andmebaasist täiendavaid üksikasju otsimata või identiteedi pakkujaga ühendust võtmata võib klient klienditaotluse vastu võtta või tagasi lükata. Ainus nõue on, et identiteedi- ja teenuseosutajatel oleks krüpteerimisleping, et teenus saaks kontrollida allkirja või isegi dekrüpteerida, milline identiteet oli krüptitud.See voog võimaldab suurt paindlikkust, hoides asjad siiski turvaliselt ja hõlpsasti arendatavana. Selle lähenemisviisi abil on teenusepakkujate klastrisse lihtne lisada uusi serverisõlme, initsialiseerides need ainult võimalusega kontrollida allkirja ja dekrüpteerida märgid, pakkudes neile jagatud salajast võtit. Seansi replikatsiooni, andmebaasi sünkroonimist ega sõlmedevahelist suhtlust pole vaja. PUHKUS oma täies hiilguses.
corp to corp vs w2 kalkulaator
Peamine erinevus JWT ja muude meelevaldsete märkide vahel on märgi sisu standardimine. Teine soovitatav lähenemisviis on JWT-märgendi saatmine Authorization
-sse päis Beareri skeemi abil. Päise sisu peaks välja nägema järgmine:
Authorization: Bearer
Selleks, et REST-teenused toimiksid ootuspäraselt, vajame klassikaliste mitmeleheliste veebisaitidega võrreldes veidi erinevat autoriseerimisviisi.
Selle asemel, et käivitada autentimisprotsess, suunates sisselogimislehele, kui klient taotleb turvatud ressurssi, autentib REST-server kõik taotlused, kasutades selleks päringus saadaolevaid andmeid, antud juhul JWT-luba. Kui selline autentimine ebaõnnestub, pole ümbersuunamisel mõtet. REST API saadab lihtsalt HTTP-koodi 401 (volitamata) vastuse ja kliendid peaksid teadma, mida teha; näiteks näitab brauser dünaamilist div-i, mis võimaldab kasutajal sisestada kasutajanime ja parooli.
Teiselt poolt, pärast edukat autentimist klassikalistel, mitmelehelistel veebisaitidel, suunatakse kasutaja HTTP-koodi 301 (jäädavalt teisaldatud) abil tavaliselt avalehele või, mis veelgi parem, lehele, mida kasutaja algselt palus ja mille käivitas autentimisprotsessi. RESTiga pole sellel jällegi mõtet. Selle asemel jätkaksime lihtsalt päringu täitmist, nagu oleks ressurss üldse turvatud, tagastaksime HTTP-koodi 200 (OK) ja oodatava vastuse keha.
Vaatame nüüd, kuidas JWT märgil põhinevat REST API-d juurutada Java ja Kevad , samal ajal kui proovime kevadise turvalisuse vaikekäitumist taaskasutada, kus saame.
Ootuspäraselt on Spring Security raamistikuga kaasas palju pistikprogramme, mis käsitlevad vanu autoriseerimismehhanisme: seansiküpsised, HTTP Basic ja HTTP Digest. Sellel puudub aga JWT-le omakeelne tugi ja selle toimimiseks peame oma käed määrima. Täpsema ülevaate saamiseks pöörduge ametniku poole Kevadine turvalisuse dokumentatsioon .
Alustame tavapärasest Kevadine turvafiltri määratlus sisse web.xml
:
springSecurityFilterChain org.springframework.web.filter.DelegatingFilterProxy springSecurityFilterChain /*
Pange tähele, et Spring Security filtri nimi peab olema täpselt springSecurityFilterChain
ülejäänud kevadkonfiguratsioon töötab kastist väljas.
Järgmisena tuleb XML-deklaratsioon turvalisusega seotud ubade oadest. XML-i lihtsustamiseks määrame vaikimisi nimeruumi väärtuseks security
lisades xmlns='http://www.springframework.org/schema/security'
XML-juurelemendile. Ülejäänud XML näeb välja selline:
s ettevõte vs c ettevõte vs partnerlus
(1) (2) (3) (4) (5) (6) (7) (8)
@PreFilter
, @PreAuthorize
, @PostFilter
, @PostAuthorize
märkused kevadiste ubade kohta kontekstis.stateless
(me ei soovi, et seanss oleks loodud turvalisuse huvides, kuna kasutame iga päringu jaoks märke).csrf
kaitse, sest meie märgid on selle suhtes immuunsed.AbstractAuthenticationProcessingFilter
, peame selle omaduste juhtimiseks deklareerima XML-is (automaatne traat siin ei tööta). Hiljem selgitame, mida filter teeb.AbstractAuthenticationProcessingFilter
vaikekäitleja pole REST-i jaoks piisavalt hea, kuna suunab kasutaja edukale lehele; sellepärast seadsime siin oma.authenticationManager
kasutab meie filter kasutajate autentimiseks.Vaatame nüüd, kuidas rakendame ülaltoodud XML-is deklareeritud konkreetseid klasse. Pange tähele, et kevad ühendab need meie jaoks. Alustame kõige lihtsamatest.
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { // This is invoked when user tries to access a secured REST resource without supplying any credentials // We should just send a 401 Unauthorized response because there is no 'login page' to redirect to response.sendError(HttpServletResponse.SC_UNAUTHORIZED, 'Unauthorized'); } }
Nagu eespool selgitatud, tagastab see klass autentimise nurjumisel lihtsalt HTTP-koodi 401 (volitamata), tühistades Spring'i vaikesuunamise.
public class JwtAuthenticationSuccessHandler implements AuthenticationSuccessHandler { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { // We do not need to do anything extra on REST authentication success, because there is no page to redirect to } }
See lihtne alistamine eemaldab eduka autentimise vaikekäitumise (ümbersuunamine avalehele või mõnele muule lehele, mida kasutaja soovib). Kui te ei tea, miks me ei pea AuthenticationFailureHandler
-i alistama, on põhjuseks see, et vaikerakendus ei suunata kuhugi ümber, kui selle ümbersuunamise URL-i pole määratud, nii et me lihtsalt väldime URL-i määramist, mis on piisavalt hea.
public class JwtAuthenticationFilter extends AbstractAuthenticationProcessingFilter { public JwtAuthenticationFilter() { super('/**'); } @Override protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) { return true; } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { String header = request.getHeader('Authorization'); if (header == null || !header.startsWith('Bearer ')) { throw new JwtTokenMissingException('No JWT token found in request headers'); } String authToken = header.substring(7); JwtAuthenticationToken authRequest = new JwtAuthenticationToken(authToken); return getAuthenticationManager().authenticate(authRequest); } @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { super.successfulAuthentication(request, response, chain, authResult); // As this authentication is in HTTP header, after success we need to continue the request normally // and return the response as if the resource was not secured at all chain.doFilter(request, response); } }
See klass on meie JWT-autentimisprotsessi alguspunkt; filter eraldab JWT-loa päringute päistest ja delegeerib sisestatud AuthenticationManager
autentimise. Kui märki ei leita, visatakse erand, mis peatab taotluse töötlemise. Eduka autentimise jaoks vajame ka alistamist, kuna vaikeveeru Spring voog peataks filtriahela ja jätkaks ümbersuunamist. Pidage meeles, et vajame keti täielikku käivitamist, sealhulgas vastuse genereerimist, nagu eespool selgitatud.
public class JwtAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider { @Autowired private JwtUtil jwtUtil; @Override public boolean supports(Class authentication) { return (JwtAuthenticationToken.class.isAssignableFrom(authentication)); } @Override protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { } @Override protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { JwtAuthenticationToken jwtAuthenticationToken = (JwtAuthenticationToken) authentication; String token = jwtAuthenticationToken.getToken(); User parsedUser = jwtUtil.parseToken(token); if (parsedUser == null) { throw new JwtTokenMalformedException('JWT token is not valid'); } List authorityList = AuthorityUtils.commaSeparatedStringToAuthorityList(parsedUser.getRole()); return new AuthenticatedUser(parsedUser.getId(), parsedUser.getUsername(), token, authorityList); } }
Selles klassis kasutame Spring'i vaikeväärtust AuthenticationManager
, kuid süstime sellele oma AuthenticationProvider
see teeb tegeliku autentimisprotsessi. Selle rakendamiseks laiendame AbstractUserDetailsAuthenticationProvider
, mis nõuab ainult tagasipöördumist UserDetails
autentimistaotluse põhjal on meie puhul JwtAuthenticationToken
sisse mähitud JWT-märgis klass. Kui märk ei kehti, viskame erandi. Kui see on siiski õige ja dekrüpteeritakse JwtUtil
abil on edukas, eraldame kasutajaandmed (näeme täpselt, kuidas klassis JwtUtil
), andmebaasile üldse juurde pääsemata. Kogu teave kasutaja kohta, sealhulgas tema rollid, sisaldub märgis endas.
milleks kasutatakse c programmeerimiskeelt
public class JwtUtil { @Value('${jwt.secret}') private String secret; /** * Tries to parse specified String as a JWT token. If successful, returns User object with username, id and role prefilled (extracted from token). * If unsuccessful (token is invalid or not containing all required user properties), simply returns null. * * @param token the JWT token to parse * @return the User object extracted from specified token or null if a token is invalid. */ public User parseToken(String token) { try { Claims body = Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); User u = new User(); u.setUsername(body.getSubject()); u.setId(Long.parseLong((String) body.get('userId'))); u.setRole((String) body.get('role')); return u; } catch (JwtException | ClassCastException e) { return null; } } /** * Generates a JWT token containing username as subject, and userId and role as additional claims. These properties are taken from the specified * User object. Tokens validity is infinite. * * @param u the user for which the token will be generated * @return the JWT token */ public String generateToken(User u) { Claims claims = Jwts.claims().setSubject(u.getUsername()); claims.put('userId', u.getId() + ''); claims.put('role', u.getRole()); return Jwts.builder() .setClaims(claims) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } }
Lõpuks: JwtUtil
klass vastutab loa parsimise eest User
objekti ja genereerides loa User
-st objekt. See on lihtne, kuna see kasutab jjwt
raamatukogu teha kogu JWT töö. Meie näites salvestame lihtsalt kasutajanime, kasutajatunnuse ja kasutajarollid märgis. Võiksime salvestada ka meelevaldsemat kraami ja lisada rohkem turvaelemente, näiteks märgi aegumine. Märgi parsimist kasutatakse AuthenticationProvider
-s nagu eespool näidatud. generateToken()
meetod kutsutakse sisselogimise ja registreerumise REST-teenustest, mis pole turvatud ja ei käivita turvakontrolli ega nõua, et taotluses oleks luba. Lõpuks genereerib see loa, mis tagastatakse klientidele, lähtudes kasutajast.
Ehkki vanad standardiseeritud turvapõhimõtted (seansiküpsis, HTTP Basic ja HTTP Digest) töötavad ka REST-teenustega, on neil kõigil probleeme, mida oleks parem vältida parema standardi kasutamisel. JWT saabub päeva päästmiseks just õigel ajal ja mis kõige tähtsam - see on väga lähedal IETF-i standardiks saamisele.
JWT peamine tugevus on kasutajate autentimise käsitlemine kodakondsuseta ja seetõttu skaleeritaval viisil, hoides samal ajal kõike turvalisena ajakohaste krüptograafiastandarditega. Nõuete (kasutaja rollide ja lubade) salvestamine märgis loob tohutuid eeliseid hajutatud süsteemiarhitektuurides, kus päringu väljastanud serveril puudub juurdepääs autentimise andmeallikale.
REST, mis tähistab lühendit Representational State Transfer, on arhitektuuristiil veebiteenuste vaheliste järjepidevate API-de paljastamiseks.
JSON-i veebitunnus (JWT) on standard teabe kodeerimiseks, mida võidakse turvaliselt JSON-objektina edastada.