Өтінішімді жазу барысында мен пайдаланушыны электрондық пошта арқылы да, әлеуметтік желілер арқылы да тіркеуге алу туралы нақты мақалалардың жетіспеушілігіне тап болдым. Классикалық кіру пішінін орнату бойынша жақсы оқулықтар болды. OAuth2 бойынша жақсы оқулықтар болды . Екі әдісті біріктіру туралы қылмыстық ақпарат аз болды. Іздеу барысында біз тиімді шешім таба алдық. Ол түпкілікті ақиқат деп айтпайды, бірақ ол өз қызметін атқарады. Бұл мақалада мен жазбаны сақтау қызметін нөлден бастап ұқсас Spring Security конфигурациясымен қалай енгізу керектігін көрсетемін. Ескерту: егер оқырман көктем туралы кем дегенде бірнеше оқулықтан өткен болса жақсы, өйткені репозиторийлер, контроллерлер және т.б. туралы егжей-тегжейлі түсіндірмесіз көктемгі қауіпсіздікке назар аударылады. Әйтпесе, қазірдің өзінде үлкен мақала шығады. алып болыңыз. Мазмұны
Деректер базасында жаңа пайдаланушы пайда болғанын көреміз. Құпия сөз шифрланған.
Жазбалар дерекқорға сақталады.
Жобаның сәтті басталып, іске асырылып жатқанын көріп отырмыз. Толық бақыт үшін бізге тек әлеуметтік желілер арқылы кіру мүмкіндігі қажет. Ал, бастайық!
Енді User және OAuth2Authentication екеуі де Басты тұлға ретінде әрекет ете алады. Біз UserService қызметінде пайдаланушының Google деректері арқылы жүктелуін алдын ала ескергендіктен, қолданба пайдаланушылардың екі түрі үшін де жұмыс істейді. OAuth2 арқылы жүйеге кірген пайдаланушыларды жазбалар бетіне қайта бағыттайтындай жобаның негізгі бет контроллерін өзгертеміз.
Пайдаланушы кәдімгі пішін арқылы да, Google есептік жазбасы арқылы да сәтті кіреді. Бұл біздің қалағанымыз! Бұл мақала веб-қосымшаны жасау, оны Spring Security көмегімен қорғау және әртүрлі кіру әдістерін біріктіру туралы кейбір мәселелерді шешті деп үміттенемін. Толық жоба codeы арқылы сіз жасай аласыз
- Жоба құру
- Нысандарды және қолданба логикасын құру
- Классикалық кіру үшін Spring Security теңшеу
- Негізгі конфигурация SecurityConfig
- Пайдаланушының реттелетін кіруі
- Контроллерді жетілдірейік
- Іске қосу
- Spring Security бағдарламасында мысал ретінде Google арқылы OAuth2 орнату
- Конфигурация және application.properties сүзгісі
- Қолданбаны Google Cloud Platform арқылы тіркеудің маңызды сәттері
- CustomUserInfoTokenServices
- Жобаның түпкілікті іске қосылуы
Жоба құру
Біз start.spring.io сайтына өтіп , жобаның негізін жасаймыз:- Web – кірістірілген Tomcat, url салыстырулары және т.б.-да қолданбаны іске қосу;
- JPA – мәліметтер базасына қосылу;
- Mustache — веб-беттерді жасау үшін қолданылатын үлгі қозғалтқышы;
- Қауіпсіздік - қолданбаны қорғау. Бұл мақала осы үшін жасалған.
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.34</version>
</dependency>
application.properties конфигурациясы қазіргі уақытта келесідей:
spring.datasource.url=jdbc:mysql://localhost:3306/springsectut?createDatabaseIfNotExist=true&useSSL=false&autoReconnect=true&useLegacyDatetimeCode=false&serverTimezone=UTC&useUnicode=yes&characterEncoding=UTF-8
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.username=yourUsername
spring.datasource.password=yourPassword
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.connection.characterEncoding=utf-8
spring.jpa.properties.connection.CharSet=utf-8
spring.jpa.properties.connection.useUnicode=true
spring.mustache.expose-request-attributes=true
Нысандарды және қолданба логикасын құру
Субъектілер
entities
Деректер қорының нысандарын орналастыратын буманы құрайық . Пайдаланушы Spring Security конфигурациясына қажет User
интерфейсті жүзеге асыратын сыныппен сипатталады . UserDetails
Пайдаланушының идентификаторы, пайдаланушы аты (бұл электрондық пошта), құпия сөз, аты, рөлі, әрекет жалаушасы, Google тіркелгісінің аты және электрондық поштасы ( googleName
және googleUsername
) болады.
@Entity
@Table(name = "user")
public class User implements UserDetails
{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String username;
private String password;
private String name;
private boolean active;
private String googleName;
private String googleUsername;
@ElementCollection(targetClass = Role.class, fetch = FetchType.EAGER)
@CollectionTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id"))
@Enumerated(EnumType.STRING)
private Set<Role> roles;
//Геттеры, сеттеры, toString(), equals(), hashcode(), имплементация UserDetails
}
Пайдаланушы рөлдері Spring Security жүйесіндегі қатынасты реттеу үшін пайдаланылады. Біздің қолданба тек бір рөлді пайдаланады:
public enum Role implements GrantedAuthority
{
USER;
@Override
public String getAuthority()
{
return name();
}
}
Идентификаторы, жазба тақырыбы, жазба мәтіні және ол тиесілі пайдаланушының идентификаторы бар жазба сыныбын жасайық:
@Entity
@Table(name = "note")
public class Note
{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String title;
private String note;
private Long userId;
//Геттеры, сеттеры, toString(), equals(), hashcode()
}
Репозиторийлер
Нысандарды дерекқорға сақтау үшін бізге барлық лас жұмыстарды орындайтын репозиторийлер қажет. Пакет жасайық , онда интерфейстен мұраланғанrepos
интерфейстерді жасаймыз . UserRepo
NoteRepo
JpaRepository<Entity, Id>
@Service
@Repository
public interface UserRepo extends JpaRepository<User, Long>
{}
@Service
@Repository
public interface NoteRepo extends JpaRepository<Note, Long>
{
List<Note> findByUserId(Long userId);
}
Контроллерлер
Біздің жазбалар қызметінде келесі беттер болады:- Үй;
- Тіркеу;
- Кіру;
- Пайдаланушы жазбаларының тізімі.
controllers
сыныпты қамтитын буманы жасайық . IndexController
Класс RegistrationController
пайдаланушыны тіркеуге жауапты. Кейінгі салыстыру пішіннен деректерді алады, пайдаланушыны дерекқорға сақтайды және кіру бетіне қайта бағыттайды. PasswordEncoder
кейінірек сипатталатын болады. Ол құпия сөздерді шифрлау үшін қолданылады.
@Controller
public class RegistrationController
{
@Autowired
private UserRepo userRepo;
@Autowired
private PasswordEncoder passwordEncoder;
@GetMapping("/registration")
public String registration()
{
return "registration";
}
@PostMapping("/registration")
public String addUser(String name, String username, String password)
{
User user = new User();
user.setName(name);
user.setUsername(username);
user.setPassword(passwordEncoder.encode(password));
user.setActive(true);
user.setRoles(Collections.singleton(Role.USER));
userRepo.save(user);
return "redirect:/login";
}
Жазбалар тізімінің бетіне жауапты контроллер қазіргі уақытта Spring Security іске асырылғаннан кейін күрделене түсетін жеңілдетілген функционалдылықты қамтиды.
@Controller
public class NoteController
{
@Autowired
private NoteRepo noteRepo;
@GetMapping("/notes")
public String notes(Model model)
{
List<Note> notes = noteRepo.findAll();
model.addAttribute("notes", notes);
return "notes";
}
@PostMapping("/addnote")
public String addNote(String title, String note)
{
Note newNote = new Note();
newNote.setTitle(title);
newNote.setNote(note);
noteRepo.save(newNote);
return "redirect:/notes";
}
}
Біз кіру бетіне контроллерді жазбаймыз, себебі оны Spring Security пайдаланады. Оның орнына бізге арнайы конфигурация қажет болады. Әдеттегідей, басқа буманы жасап, оны шақырайық config
және сыныпты сол жерге орналастырайық MvcConfig
. Spring Security конфигурациясын жазғанда, ол «/login» пайдаланған кезде қай бетке сілтеме жасайтынымызды біледі.
@Configuration
public class MvcConfig implements WebMvcConfigurer
{
public void addViewControllers(ViewControllerRegistry registry)
{
registry.addViewController("/login").setViewName("login");
registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
}
}
Беттер
Мен беттерді жасау үшін «Мұрт» үлгісін қолданамын . Сіз басқасын іске асыра аласыз, бұл маңызды емес. Барлық беттерде қолданылатын мета ақпарат үшін meta.mustache файлы жасалды. Оған сонымен қатар жобамыздың беттерін әдемірек ету үшін Bootstrap кіреді. Беттер "src/main/resources/templates" каталогында жасалады. Файлдардың кеңейтілген мұрты бар. Html codeын тікелей мақалаға орналастыру оны тым үлкен етеді, сондықтан жобаның GitHub репозиторийіндегі үлгілер қалтасына сілтеме .Классикалық кіру үшін Spring Security теңшеу
Spring Security қолданбаны және оның ресурстарын рұқсатсыз кіруден қорғауға көмектеседі.SecurityConfig
Біз мұраға алынған сыныпта қысқаша жұмыс конфигурациясын жасаймыз WebSecurityConfigurerAdapter
, оны пакетке орналастырамыз config
. Оны Spring Security қолдауын қосатын @EnableWebSecurity annotationсымен және осы сыныпта кейбір конфигурация бар екенін көрсететін @Configuration annotationсымен белгілейік. Ескертпе: автоматты түрде конфигурацияланған pom.xml ішінде Қауіпсіздіктің белгіленген жолмен жүзеге асырылуына жол бермейтін Spring Boot негізгі компонентінің 2.1.4.RELEASE нұсқасы бар. Жобада қайшылықтарды болдырмау үшін нұсқаны 2.0.1.RELEASE нұсқасына өзгерту ұсынылады.
Негізгі конфигурация SecurityConfig
Біздің конфигурациямыз мыналарды жасай алады:-
Құпия сөздерді шифрлау
BCryptPasswordEncoder
:@Autowired private PasswordEncoder passwordEncoder; @Bean PasswordEncoder passwordEncoder() { PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); return passwordEncoder; }
-
Арнайы жазылған аутентификация провайдерін пайдаланып жүйеге кіріңіз:
@Autowired private AuthProvider authProvider; @Override protected void configure(AuthenticationManagerBuilder auth) { auth.authenticationProvider(authProvider); }
-
Анонимді пайдаланушыларға басты бетке, тіркеу және кіру беттеріне кіруге рұқсат беріңіз. Барлық басқа сұрауларды жүйеге кірген пайдаланушылар орындауы керек. Кіру беті ретінде бұрын сипатталған «/login» тағайындайық. Жүйеге кіру сәтті болса, пайдаланушы ескертулер тізімі бар бетке өтеді, қате болса, пайдаланушы кіру бетінде қалады. Сәтті шыққаннан кейін пайдаланушы негізгі бетке өтеді.
@Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/resources/**", "/", "/login**", "/registration").permitAll() .anyRequest().authenticated() .and().formLogin().loginPage("/login") .defaultSuccessUrl("/notes").failureUrl("/login?error").permitAll() .and().logout().logoutSuccessUrl("/").permitAll(); }
Пайдаланушының реттелетін кіруі
Өздігінен жазылғанAuthProvider
пайдаланушыға электрондық пошта арқылы ғана емес, сонымен қатар пайдаланушы атымен де кіруге мүмкіндік береді.
@Component
public class AuthProvider implements AuthenticationProvider
{
@Autowired
private UserService userService;
@Autowired
private PasswordEncoder passwordEncoder;
public Authentication authenticate(Authentication authentication) throws AuthenticationException
{
String username = authentication.getName();
String password = (String) authentication.getCredentials();
User user = (User) userService.loadUserByUsername(username);
if(user != null && (user.getUsername().equals(username) || user.getName().equals(username)))
{
if(!passwordEncoder.matches(password, user.getPassword()))
{
throw new BadCredentialsException("Wrong password");
}
Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
return new UsernamePasswordAuthenticationToken(user, password, authorities);
}
else
throw new BadCredentialsException("Username not found");
}
public boolean supports(Class<?> arg)
{
return true;
}
}
Сіз байқағандай, UserService
пакетте орналасқан сынып пайдаланушыны жүктеуге жауап береді services
. Біздің жағдайда ол пайдаланушыны username
кірістірілген енгізу сияқты өріс бойынша ғана емес, сонымен қатар пайдаланушы аты, Google тіркелгісінің аты және Google тіркелгісінің электрондық поштасы бойынша іздейді. Соңғы екі әдіс OAuth2 арқылы кіруді жүзеге асырған кезде бізге пайдалы болады. Мұнда сынып қысқартылған нұсқада берілген.
@Service
public class UserService implements UserDetailsService
{
@Autowired
private UserRepo userRepo;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
{
User userFindByUsername = userRepo.findByUsername(username);
//Остальные поиски
if(userFindByUsername != null)
{
return userFindByUsername;
}
//Остальные проверки
return null;
}
}
Ескерту: қажетті әдістерді жазуды ұмытпаңыз UserRepo
!
Контроллерді жетілдірейік
Біз көктемгі қауіпсіздікті конфигурацияладық. Енді жазбалар контроллерінде мұны пайдаланудың уақыты келді. Енді әрбір салыстыру қосымша Негізгі параметрді қабылдайды, ол арқылы пайдаланушыны табуға тырысады. Неліктен мен сыныпты тікелей енгізе алмаймынUser
? Содан кейін әлеуметтік желілер арқылы логин жазғанда пайдаланушы түрлерінің сәйкес келмеуінен қақтығыс болады. Біз алдын ала қажетті икемділікті қамтамасыз етеміз. Біздің жазбалар контроллерінің codeы енді келесідей көрінеді:
@GetMapping("/notes")
public String notes(Principal principal, Model model)
{
User user = (User) userService.loadUserByUsername(principal.getName());
List<Note> notes = noteRepo.findByUserId(user.getId());
model.addAttribute("notes", notes);
model.addAttribute("user", user);
return "notes";
}
@PostMapping("/addnote")
public String addNote(Principal principal, String title, String note)
{
User user = (User) userService.loadUserByUsername(principal.getName());
Note newNote = new Note();
newNote.setTitle(title);
newNote.setNote(note);
newNote.setUserId(user.getId());
noteRepo.save(newNote);
return "redirect:/notes";
}
Ескертпе: жобада CSRF қорғанысы әдепкі бойынша қосылған , сондықтан оны өзіңіз үшін өшіріңіз (http.csrf().disable()) немесе мақала авторы ретінде csrf белгісі бар жасырын өрісті қосуды ұмытпаңыз. барлық пост сұрауларына.
Іске қосу
Біз жобаны іске қосуға тырысамыз.Spring Security бағдарламасында мысал ретінде Google арқылы OAuth2 орнату
OAuth2 бағдарламасын іске асырған кезде мен Spring сайтындағы осы ресми оқу құралына сүйендім . OAuth2-ге қолдау көрсету үшін pom.xml файлына келесі кітапхананы қосыңыз:<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
ішінде Spring Security конфигурациясын өзгертейік SecurityConfig
. Алдымен @EnableOAuth2Client annotationсын қосамыз. Ол әлеуметтік желілер арқылы кіру үшін қажет нәрсені автоматты түрде шығарады.
Конфигурация және application.properties сүзгісі
Қауіпсіздік конфигурациясында пайдалану үшін OAuth2ClientContext енгізейік.@Autowired
private OAuth2ClientContext oAuth2ClientContext;
OAuth2ClientContext пайдаланушының әлеуметтік кіру сұрауын растайтын сүзгіні жасау кезінде пайдаланылады. Сүзгі @EnableOAuth2Client annotationсының арқасында қол жетімді. Бізге тек көктемгі қауіпсіздік сүзгісінің алдында оны дұрыс ретпен шақыру керек . Сонда ғана біз OAuth2 арқылы кіру процесі кезінде қайта бағыттауларды ұстай аламыз. Ол үшін біз FilterRegistrationBean
сүзгінің басымдылығын -100-ге орнататын қолданамыз.
@Bean
public FilterRegistrationBean oAuth2ClientFilterRegistration(OAuth2ClientContextFilter oAuth2ClientContextFilter)
{
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(oAuth2ClientContextFilter);
registration.setOrder(-100);
return registration;
}
private Filter ssoFilter()
{
OAuth2ClientAuthenticationProcessingFilter googleFilter = new OAuth2ClientAuthenticationProcessingFilter("/login/google");
OAuth2RestTemplate googleTemplate = new OAuth2RestTemplate(google(), oAuth2ClientContext);
googleFilter.setRestTemplate(googleTemplate);
CustomUserInfoTokenServices tokenServices = new CustomUserInfoTokenServices(googleResource().getUserInfoUri(), google().getClientId());
tokenServices.setRestTemplate(googleTemplate);
googleFilter.setTokenServices(tokenServices);
tokenServices.setUserRepo(userRepo);
tokenServices.setPasswordEncoder(passwordEncoder);
return googleFilter;
}
Сондай-ақ конфигурациялау (HttpSecurity http) функциясына жаңа сүзгіні қосу керек:
http.addFilterBefore(ssoFilter(), UsernamePasswordAuthenticationFilter.class);
Сүзгі сонымен қатар клиенттің Google арқылы тіркелгенін білуі керек. @ConfigurationProperties annotationсы application.properties ішінде қай конфигурация сипаттарын іздеу керектігін көрсетеді.
@Bean
@ConfigurationProperties("google.client")
public AuthorizationCodeResourceDetails google()
{
return new AuthorizationCodeResourceDetails();
}
Аутентификацияны аяқтау үшін Google пайдаланушы ақпаратының соңғы нүктесін көрсету керек:
@Bean
@ConfigurationProperties("google.resource")
public ResourceServerProperties googleResource()
{
return new ResourceServerProperties();
}
Қолданбамызды Google Cloud Platform жүйесінде тіркегеннен кейін application.properties файлына сәйкес префикстермен сипаттарды қосамыз:
google.client.clientId=yourClientId
google.client.clientSecret=yourClientSecret
google.client.accessTokenUri=https://www.googleapis.com/oauth2/v4/token
google.client.userAuthorizationUri=https://accounts.google.com/o/oauth2/v2/auth
google.client.clientAuthenticationScheme=form
google.client.scope=openid,email,profile
google.resource.userInfoUri=https://www.googleapis.com/oauth2/v3/userinfo
google.resource.preferTokenInfo=true
Қолданбаны Google Cloud Platform арқылы тіркеудің маңызды сәттері
Жол: API және қызметтер -> Тіркелгі деректері OAuth рұқсат сұрау терезесі:- Қолданбаның атауы: көктемгі кіру пішіні және OAuth2 оқулығы
- Қолдау көрсету электрондық пошта мекенжайы: сіздің электрондық поштаңыз
- Google API қолдану аясы: электрондық пошта, профиль, openid
- Рұқсат етілген домендер: me.org
- Қосымшаның негізгі бетіне сілтеме: http://me.org:8080
- Қолданбаның құпиялылық саясатына сілтеме: http://me.org:8080
- Қолданбаны пайдалану шарттарына сілтеме: http://me.org:8080
- Түрі: Веб қолданбасы
- Тақырып: көктемгі кіру пішіні және OAuth2 оқулығы
- Рұқсат етілген JavaScript көздері: http://me.org, http://me.org:8080
- Рұқсат етілген қайта бағыттау URI мекенжайлары: http://me.org:8080/login, http://me.org:8080/login/google
CustomUserInfoTokenServices
Сүзгі функциясының сипаттамасында Custom сөзін байқадыңыз ба? СыныпCustomUserInfoTokenServices
. Иә, біз блэкджекпен және пайдаланушыны дерекқорда сақтау мүмкіндігімен өз класымызды жасаймыз! UserInfoTokenServices
IntelliJ IDEA ішіндегі Ctrl-N пернелер тіркесімін пайдаланып, әдепкі мәннің қалай жүзеге асырылатынын табуға және көруге болады . Оның codeын жаңадан жасалған сыныпқа көшірейік CustomUserInfoTokenServices
. Оның көп бөлігін өзгеріссіз қалдыруға болады. Функциялардың логикасын өзгертпес бұрын UserRepo
және класстың жеке өрістері ретінде қосайық PasswordEncoder
. Олар үшін орнатушыларды жасайық. SecurityConfig сыныбына @Autowired UserRepo userRepo қосамыз. Сүзгі жасау әдісіндегі қатенің көрсеткіші қалай жоғалып кететінін қарастырамыз және біз қуанамыз. Неліктен @Autowired қолданбасын CustomUserInfoTokenServices қызметіне тікелей қолдану мүмкін болмады? Өйткені бұл класс тәуелділікті қабылдамайды, өйткені оның өзі ешқандай Spring annotationсымен белгіленбейді және оның конструкторы сүзгі жарияланған кезде анық жасалады. Тиісінше, Spring's DI механизмі бұл туралы білмейді. Осы сыныптағы кез келген нәрсеге @Autowired annotationсын жазсақ, біз пайдаланған кезде NullPointerException аламыз. Бірақ айқын орнатушылар арқылы бәрі өте жақсы жұмыс істейді. Қажетті құрамдастарды іске асырғаннан кейін қызығушылықтың негізгі an objectісі пайдаланушы туралы ақпараты бар Map<String, Object> шығарылатын loadAuthentication функциясы болады. Дәл осы жобада мен әлеуметтік желі арқылы кірген пайдаланушыны мәліметтер базасына сақтауды жүзеге асырдым. Біз Google тіркелгісін OAuth2 провайдері ретінде пайдаланатындықтан, картада Google-ға тән "қосалқы" өріс бар-жоғын тексереміз. Егер ол бар болса, бұл пайдаланушы туралы ақпарат дұрыс қабылданғанын білдіреді. Біз жаңа пайдаланушы жасаймыз және оны дерекқорға сақтаймыз.
@Override
public OAuth2Authentication loadAuthentication(String accessToken)
throws AuthenticationException, InvalidTokenException
{
Map<String, Object> map = getMap(this.userInfoEndpointUrl, accessToken);
if(map.containsKey("sub"))
{
String googleName = (String) map.get("name");
String googleUsername = (String) map.get("email");
User user = userRepo.findByGoogleUsername(googleUsername);
if(user == null)
{
user = new User();
user.setActive(true);
user.setRoles(Collections.singleton(Role.USER));
}
user.setName(googleName);
user.setUsername(googleUsername);
user.setGoogleName(googleName);
user.setGoogleUsername(googleUsername);
user.setPassword(passwordEncoder.encode("oauth2user"));
userRepo.save(user);
}
if (map.containsKey("error"))
{
this.logger.debug("userinfo returned error: " + map.get("error"));
throw new InvalidTokenException(accessToken);
}
return extractAuthentication(map);
}
Бірнеше провайдерді пайдаланған кезде бір CustomUserInfoTokenServices ішінде әртүрлі опцияларды көрсетуге және сүзгі мәлімдемесі әдісінде ұқсас қызметтердің әртүрлі сыныптарын тіркеуге болады.
@GetMapping("/")
public String index(Principal principal)
{
if(principal != null)
{
return "redirect:/notes";
}
return "index";
}
GO TO FULL VERSION