어플 아이콘 변경

프로그래밍/Android (Java)|2019. 6. 25. 10:38
반응형

res\drawable 에 이미지를 넣고 AndroidManifest.xml 파일에서 설정한다.

 

<application

    ....

    android:icon="@drawable/ic_launcher_new"
    ....

 

 

반응형

댓글()

구글맵 API document

프로그래밍/Android (Java)|2019. 6. 25. 10:08
반응형

https://developers.google.com/maps/documentation/android-sdk/intro

 

 

반응형

댓글()

구글 아이콘 무료 사용 가능

프로그래밍/Android (Java)|2019. 6. 24. 14:13
반응형

https://material.io/tools/icons/?style=round

 

반응형

댓글()

Google OTP를 사용한 SSH 인증

리눅스/OS 일반|2019. 6. 21. 17:31
반응형

OTP One-Time-Password의 약자로 특정한 알고리즘에 의해 일정 시간 마다 생성되는 일회성 암호로 사용자를 확인하는 인증 방법입니다. OTP 기술을 활용한 다양한 보안 프로그램들이 있으며, 그 중에서 유명한 OTP 앱으로는 Google OTP가 있습니다. (한국 플레이스토어 앱 이름은 구글 OTP라고 자주 부르지만 정확한 영문 명칭은 Google Authenticator입니다.)

SSH 프로토콜로 원격 서버에 접속할 때 2FA(Two-Factor-Authentication)인증을 구성하기 위한 방법 중 하나로 Google OTP 코드를 사용할 수 있습니다.

2FA 인증을 설정하면서 보안상으로 볼 수 있는 이점이 크며 여기에 OTP 방식의 인증을 추가하는 것이므로 기존의 패스워드 방식의 인증보다는 더욱 안전한 계정 보호가 가능할 것입니다.

 

 

리눅스 Google Authenticator 설치

먼저 리눅스용 Google Authenticator를 설치해야 합니다. 아래 두 방법 중에서 하나만 선택하여 진행하면 됩니다.

 

[방법 1] : 패키지 설치 (권장)

패키지 설치는 의존성 체크와 함께 간편한 설치가 가능하므로 권장되는 방법입니다.

각 리눅스 배포판에서 기본으로 제공되는 패키지 관리자를 사용할 것입니다. 아래 내용을 참고하여 패키지 설치 명령을 실행합니다. (패키지 설치는 root 권한을 가진 사용자로 진행합니다.)

RedHat 계열 (RHEL / CentOS / Fedora)

  • [root@localhost ~]# yum install google-authenticator

만약 위와 같이 입력했는데도 설치할 패키지 파일이 존재하지 않는다고 하면, epel-release 패키지를 먼저 설치한 후 진행해보시기 바랍니다.

  • [root@localhost ~]# yum install epel-release

 

Debian 계열 (Debian / Ubuntu)

  • [root@localhost ~]# apt install libpam-google-authenticator

 

CentOS에서 설치된 예시는 다음과 같습니다.

  • # yum install google-authenticator

  • Loaded plugins: fastestmirror, langpacks

  • Determining fastest mirrors

  • * base: mirror.kakao.com

  • * epel: mirror.premi.st

  • * extras: mirror.kakao.com

  • * updates: mirror.kakao.com

  • epel 12770/12770

  • google-chrome 3/3

  • Package google-authenticator-1.04-1.el7.x86_64 already installed and latest version

  • Nothing to do

 

[방법 2] : 수동 설치

만약 위 방법으로 설치가 어려운 경우 다음 방법으로 수동 설치를 진행합니다. 먼저 관련된 패키지가 없다면 다음 패키지를 모두 설치해줍니다.

  • pam-devel

  • gcc

  • autoconf

  • automake

  • libtool

  • git

패키지 관리자를 사용할 수 있다면 다음과 같이 사용합니다.

  • [root@localhost ~]# yum install pam-devel gcc autoconf automake libtool git

이후 git clone 명령어로 google-authenticator-libpam 파일을 다운로드합니다.

다운로드한 폴더로 이동하여 패키지를 컴파일한 후에 설치합니다.

  • [root@localhost ~]# cd google-authenticator-libpam

  • [root@localhost ~]# ./bootstrap.sh

  • [root@localhost ~]# ./configure --libdir=/lib64

  • [root@localhost ~]# make install

 

 

 

SSH 인증을 위한 PAM 모듈 설정

기본적인 패키지 설치가 완료되었다면 이제 SSH 인증을 위한 몇가지 구성이 필요합니다. PAM(Pluggable Authentication Modules) 모듈을 사용하기 위해서 Google Authenticator 라이브러리를 추가해주어야 합니다.

먼저 SSH 서비스 데몬과 관련된 PAM 설정 파일 /etc/pam.d/sshd를 수정해야 합니다. 텍스트 에디터를 사용하여 파일을 열어보겠습니다.

  • [root@localhost ~]# vim /etc/pam.d/sshd

이후 다음 한 줄을 하단에 포함합니다.

  • auth required pam_google_authenticator.so nullok

여기서 우측의 nullok를 포함하거나 뺄 수 있습니다. nullok가 포함되면 리눅스 내의 모든 사용자가 각각의 secret key를 생성해야 합니다. 만약 동일한 secret key를 다 같이 사용하고자 할 때에는 nullok를 제외시켜주면 됩니다.

추가하고 나면 다음과 같이 보여질 것입니다. (일부 운영체제 환경에 따라 설정 파일 내 모습이 다르게 보일 수는 있습니다.)

이제 SSH 서버의 설정 파일 sshd_config 파일을 텍스트 에디터로 수정해보겠습니다. 여기에도 몇가지 요구되는 것들이 있습니다.

  • [root@localhost ~]# vim /etc/ssh/sshd_config

변경해야 할 내용들은 다음과 같습니다.

  • PasswordAuthentication : 패스워드 인증을 사용할 것인가 (OTP 인증 시에는 패스워드 인증을 사용하지 않음)

  • ChallengeResponseAuthentication : 시도-응답 인증 방식을 사용할 것인가 (2FA인증을 위해서도 필요)

  • UsePAM : 인증에 PAM 모듈을 사용할 것인가

  • PermitEmptyPasswords : 비어있는 사용자 암호를 허용할 것인가

파일이 열리면 아래 설정 값과 같은지 확인해봅니다. 만약 아래와 값이 다르다면, 수정해줍니다. (PasswordAuthentication의 경우 필요하다면 yes로 지정해도 됩니다.)

(추가로 PermitEmptyPasswords 의 항목에 대한 값이 yes 인 경우 추후 Google OTP 인증이 원활하지 않을 수 있습니다. 가능하면 이 항목의 값을 no 로 변경해주는 것이 좋습니다.)

  • PasswordAuthentication no

  • ChallengeResponseAuthentication yes

  • UsePAM yes

  • PermitEmptyPasswords no

 

이제 SSH 서버 프로세스 재시작해야 합니다. 다음 명령어를 사용하여 서비스를 재시작합니다.

  • [root@localhost ~]# service sshd restart

만약 systemd 를 사용 중인 최신 리눅스인 경우 다음과 같이 사용해도 됩니다.

  • [root@localhost ~]# systemctl restart sshd

만일의 에러에 대비하여 네트워크 시간을 동기화하여 시간 오차로 인한 인증 실패 문제를 예방할 수 있습니다. 하단의 명령어를 참고하여 NTP 서비스를 재시작합니다.

  • # RedHat / CentOS

  • [root@localhost ~]# service ntpd restart

  • # Debian / Ubuntu

  • [root@localhost ~]# service ntp restart

 

 

OTP 인증파일이란?

인증을 위한 서버 설정은 어느정도 완료되었지만, SSH 서버가 갱신되었다 하더라도 각 사용자의 홈 디렉토리에 .google_authenticator 파일이 없으면 OTP 인증을 진행하지 않습니다. Challenge-Response-Authentication의 기본 설정인 패스워드만 물어볼 것입니다.

때문에 OTP 사용자 인증을 위해서는 Google Authenticator 인증 파일을 생성해야 합니다.

 

이 파일을 생성하는 도중에 OTP 인증에 대한 시크릿 키(Secret Key)가 나타날 것입니다. 시크릿 키는 쉽게 말하자면 OTP 토큰 값이 나타나는 패턴에 대한 일련의 해시값입니다.

시크릿 키는 공개 키 사용자 인증(Public Key Authentication)에서의 개인 키와 비슷한 역할을 하므로 인증할 대상자를 제외하고는 시크릿 키를 절대 공유해서는 안됩니다. 따라서 .google-authenticator 파일 또한 권한을 소유자에게만 지정해주어야 합니다. (이는 생성 시 알아서 진행될 것입니다.)

OTP 앱에서 이 서버와 유효한 인증 정보를 교환하기 위해서는 시크릿 키가 앱과 서버 모두 동일해야 합니다.

 

Google Authenticator 인증 파일 생성 (과정 1)

먼저 인증할 서버의 터미널에서 다음과 같이 입력합니다. 여기서 OTP 인증을 사용할 사용자 계정으로 명령을 입력해야 합니다. (예를 들어 user 라는 계정이 OTP를 사용하려면 해당 계정으로 전환한 후 명령을 실행해야 합니다.)

  • [user@localhost ~]$ google-authenticator

먼저 다음 프롬프트가 나타날 것입니다. 아래 프롬프트는 시간 기반(Time-based) 인증을 사용할 것인지에 대한 여부이므로 y 를 입력하여 다음으로 넘어갑니다.

  • Do you want authentication tokens to be time-based (y/n) y

 

이후 시크릿 키 복구 코드가 나타날 것입니다. 시크릿 키를 쉽게 등록할 수 있는 QR코드 또한 나타납니다. (이는 Google OTP 앱에서만 사용 가능합니다.)

  • Warning: pasting the following URL into your browser exposes the OTP secret to Google: https://www.google.com/chart?chs=200x200&chld=M|0&cht=qr&chl=otpauth://totp/user@localhost.localdomain%3Fsecret%3D[SECRET KEY]%26issuer%3Dlocalhost.localdomain

  •  
  • [QR코드가 여기에 나타납니다.]

  •  
  • Your new secret key is: MHZSIJYMO#################

  • Your verification code is 684###

  • Your emergency scratch codes are:

  • 4612####

  • ####9217

  • 5569####

  • ####8321

  • 7916####

만약 QR코드가 아래와 같이 제대로 나타난다면 OTP 앱에서 쉽게 추가할 수도 있습니다. (제대로 보이지 않는다면 터미널 글꼴이나 글자 크기를 변경해보세요.)

 

 

Google OTP 클라이언트 설치하기

이제 이 시크릿 키를 OTP 앱에 저장해야 합니다. 터미널에서 작업 하던 것을 잠깐 멈추고 (터미널을 종료하지 않아야 합니다!!) 모바일에서 OTP 클라이언트를 설치해보도록 하겠습니다.

이외에도 제 3자 OTP 클라이언트에서 구글 OTP에서 사용하는 인증 알고리즘을 지원한다면 이를 설치하셔도 좋습니다. 여러 운영체제에서 사용이 가능하지만 여기서는 안드로이드 앱으로 진행해보도록 하겠습니다.

 

설치가 완료되었다면 실행하여 Secret Key를 등록해보도록 하겠습니다.

Google OTP 화면이 보이면 하단 우측에 있는 추가(+) 버튼을 클릭합니다.

QR코드를 스캔하고자 하면 ‘바코드 스캔’을 선택합니다. 그러면 즉시 OTP 코드가 추가되어 더 이상 OTP 앱에서 구성할 것은 없습니다.

만약 터미널에 나타나는 QR코드가 제대로 인식되지 않을 경우 하단의 ‘제공된 키 입력’ 을 선택합니다.

‘제공된 키 입력’을 선택했다면 다음과 같이 시크릿 키를 수동으로 입력해주어야 합니다. 계정 이름은 OTP 코드들을 구분하기 쉬운 별칭이면 어떤 이름이어도 상관없습니다.

이제 아래와 같이 OTP 코드가 나타난다면 등록 과정에는 일단 성공한 것입니다. 일정 시간이 지나면 하단의 토큰은 지속적으로 변경 될 것입니다.

 

 

Google Authenticator 인증 파일 생성 (과정 2)

아직 인증 서버 내 터미널에서의 몇가지 작업이 남아있어 계속 진행하도록 하겠습니다.

  • Do you want me to update your "/home/test/.google_authenticator" file? (y/n) y

위 프롬프트는 .google_authenticator 파일을 홈 디렉토리에 생성하겠냐는 물음입니다. y 를 입력하여 계속합니다.

만약 SELinux가 켜져있을 경우 이 파일을 읽지 못하여 문제가 발생할 수 있습니다. 인증 파일 읽기에 문제가 발생하면 추후 파일에 대한 액세스가 가능한지 확인해보셔야 합니다.

 

  • Do you want to disallow multiple uses of the same authentication

  • token? This restricts you to one login about every 30s, but it increases

  • your chances to notice or even prevent man-in-the-middle attacks (y/n) y

위 프롬프트는 30초 마다 생성되는 한 토큰에 대해 두 번 이상 인증하게 하여 중간자 공격(man-in-middle-attack)을 예방할 것인지에 대한 여부입니다. 여기서는 권장값인 y 를 입력합니다. n을 입력하면 이미 인증된 후 다음 토큰이 생성될 때 까지 인증이 거부됩니다.

 

  • By default, a new token is generated every 30 seconds by the mobile app.

  • In order to compensate for possible time-skew between the client and the server,

  • we allow an extra token before and after the current time. This allows for a

  • time skew of up to 30 seconds between authentication server and client. If you

  • experience problems with poor time synchronization, you can increase the window

  • from its default size of 3 permitted codes (one previous code, the current

  • code, the next code) to 17 permitted codes (the 8 previous codes, the current

  • code, and the 8 next codes). This will permit for a time skew of up to 4 minutes

  • between client and server.

  • Do you want to do so? (y/n) n

이번에는 시간 동기화를 진행하면서 발생하는 시간상 오차 및 왜곡을 방지하고자 하는 옵션입니다. 기존에 허용되는 OTP 토큰은 현재 시간에 생성된 토큰의 이전과 다음 각각 1개씩 허용됩니다. 예를 들어 이전 토큰이 ‘111111’이었고 현재 토큰이 ‘222222’, 다음 예상 토큰이 ‘333333’이라면 입력 가능한 토큰은 이 세가지가 모두 허용됩니다.

이러한 허용 범위(이전 1개, 현재, 다음 1개)을 17개 (이전 8개, 현재, 다음 8개)로 넓힐 것인지에 대한 여부입니다.

가능하면 n으로 설정하는 것이 좋지만 만약 시간차이로 인한 인증 실패에 걱정이 된다면 y로 지정해도 무방합니다.

  • If the computer that you are logging into isn't hardened against brute-force

  • login attempts, you can enable rate-limiting for the authentication module.

  • By default, this limits attackers to no more than 3 login attempts every 30s.

  • Do you want to enable rate-limiting? (y/n) y

마지막으로 묻는 내용은 무차별 대입 공격(brute-force-attack)을 방지하기 위한 설정입니다. 30초 마다 생성되는 토큰에 3회 이상 인증에 실패하는 경우 일시적으로 로그인을 차단하도록 하는 내용입니다. 이 역시 y 로 설정하는 것을 권장합니다.

이제 .google_authenticator 파일이 생성되어있을 것입니다. 파일에는 다음과 같이 시크릿 키 백업 키, 그리고 설정했던 내용들이 포함되어 있습니다.

 

  • MHZSIJYMO#################

  • " RATE_LIMIT 3 30

  • " WINDOW_SIZE 17

  • " DISALLOW_REUSE

  • " TOTP_AUTH

  • 4612####

  • ####9217

  • 5569####

  • ####8321

  • 7916####

문제없이 생성되었다면 이제 SSH 서버 접속 및 인증 테스트를 진행해보도록 하겠습니다.

 

 

OTP 정보를 사용하여 SSH 접속하기

OTP 인증 파일을 생성하였고 사용 준비가 모두 완료되었습니다. 이제 실제로 SSH 서버에 접속하여 테스트해보도록 하겠습니다.

Challenge-Response 인증을 사용하기 위해서는 이러한 인증을 지원하는 SSH 클라이언트가 필요합니다. 여기서는 Windows용 SSH 클라이언트인 Xshell을 사용해보도록 하겠습니다.

Xshell을 열어 상단 메뉴의 파일 - 새로 만들기를 클릭합니다.

세션 파일의 이름과 접속할 서버의 호스트 주소를 입력한 후 확인 또는 연결을 클릭합니다.

세션에 연결한 후에는 사용자 계정 및 암호를 물어볼 것입니다.

인증 방법은 ‘Keyboard Interactive’로 선택한 후 (또는 자동으로 선택될 것입니다.) 하단의 프롬프트에 나타난 내용대로 암호를 입력하면 됩니다.

먼저 Password: 프롬프트가 나타났으므로 사용자의 일반 암호를 입력합니다.

이제 ‘Verification code:‘ 가 나타났습니다. 이 것이 바로 OTP 인증 토큰을 입력하는 부분입니다.

OTP 클라이언트(여기서는 안드로이드 Google OTP 앱)를 실행한 후 방금 전에 생성했던 키의 토큰 값 6자리를 여기에 입력해주시면 됩니다.

토큰이 정상 확인되면 SSH 서버에 성공적으로 접속된 것을 확인할 수 있을 것입니다.

이상으로 모든 OTP 인증 설정이 완료되었습니다.

 

 

[출처] https://jootc.com/p/201812291719

반응형

댓글()

SAMBA 서비스 포트 변경 및 마운트 하기

리눅스/OS 일반|2019. 6. 19. 13:18
반응형

[목적]

ISP 에서 차단된 SAMBA 포트를 변경하여 사용하고자 할 때

- tcp 139 > tcp 1390

- tcp 445 > tcp 4450

 

 

1. 포트 변경

 

# vi /etc/samba/smb.conf

[global]

smb ports = 4450 1390    // 추가

 

# service smb restart

 

* iptables 에서 tcp 1390, tcp 4450 포트 오픈하는 것 잊지 말자.

 

 

2. 마운트

 

2-1) 리눅스에서 마운트

# mount -t cifs -o username=testuser,password=1234,port=1390 //220.00.000.00/testuser /111

 

* 마운트 후 quota 적용 디스크량 말고 스토리지 전체 용량이 표시될 경우 smb 프로토콜 2를 사용하기 위해 vers=2.0 옵션을 추가해줍니다.

* 220.00.000.00 에 사용하는 IP 를 입력합니다.

 

2-2) 윈도우즈에서 마운트

 

(1) 루프백 어댑터 생성 및 설정

제어판  >  하드웨어  >  장치 관리자  >  서버명 우클릭 (레거시 하드웨어 추가)  >  목록에서 선택  >  네트워크 어댑터  >  제조사 : Microsoft, 모델 : Microsoft KM-TEST 루프백 어댑터  >  마침

제어판  >  네트워크 및 인터넷  >  네트워크 상태 및 작업 보기  >  어댑터 설정 변경  >  (좀전에 추가한 루프백 어댑터) 이더넷2 우클릭 (속성)
① Microsoft 네트워크용 파일 및 프린터 공유 : 체크 해제 (삭제는 아님)
② 인터넷 프로토콜 버전 4(TCP/IPv4) 속성
- IP 주소 : 10.0.0.1 (사용할 임의의 값)
- 서브넷 마스크 : 255.255.255.0

- 기본 게이트웨이 : (없음)

- DNS : (없음)

[고급] 버튼  >  [WINS] 탭  >  LMHOSTS 조회 가능 : 체크  >  NetBIOS over TCP/IP 사용 안 함 : 체크

 

* '식별되지 않은 네트워크' 라고 표시되어도 상관 없습니다.

 

(2) Windows 에서 139, 445 포트 비활성화

 

139 포트

명령프롬프트 창에서 아래와 같이 실행

wmic nicconfig where TcpipNetbiosOptions=0 call SetTcpipNetbios 2

 

445 포트

레지스트리 실행 (시작  >  실행  >  regedit) 후 아래 항목을 찾아 값을 비움

HKEY_LOCAL_MACHINE \ SYSTEM \ CurrentControlSet \ Services \ NetBT \ Parameters
- 항목 : TransportBindName, 종류 : REG_SZ, 값 : \Device\ 를 지우고 저장    // 잘 안됨. ㅜ.ㅜ

 

이후 적용을 위해 Windows 리부팅을 하면 445 포트가 사용되지 않게 됩니다.

 

(3) rinetd 설치 및 설정
rinetd 프로그램을 다운 받아서 압축을 풀고, C:\rinetd 로 옮깁니다.

- 다운로드 : http://www.boutell.com/rinetd/http/rinetd.zip

rinetd.zip
0.11MB

 

C:\rinetd\rinetd.conf 파일을 생성 후 아래 내용으로 입력합니다.

10.0.0.1 139 220.00.000.00 1390

10.0.0.1 445 220.00.000.00 4450

* 설명

10.0.0.1 은 전에 임의로 추가한 IP

139 는 목적지 포트

220.00.000.00 는 포워딩 될 삼바 서버 IP

1390 은 삼바 서버 PORT 

* 삼바 서버 IP 대신 도메인명으로도 연결이 가능합니다.

 

설정 저장 후 CMD를 통해서 구동합니다.

cd c:\rinetd

rinetd.exe -c rinetd.conf

 

* 구동시 'rinetd: couldn't bind to address 10.0.0.1 port 445' 에러 메세지가 뜬다면

   위 445 포트 비활성화 항목을 다시 살펴보세요. 445가 사용중이라서 오류가 출력됩니다.

 

삼바서버에서 [testuser] 라고 설정하였으면, 윈도우즈 client 에서 아래와 같이 마운트가 가능합니다.

 

[시작]  >  실행  >  \\10.0.0.1\testuser

 

 

반응형

댓글()

mongodump 사용시 조건을 주어 백업하기

리눅스/MySQL|2019. 6. 18. 09:25
반응형

원격일 경우,

--collection 컬렉션이름 : 이 옵션을 안쓰면 전체 db를 백업하게 됩니다.

 

mongodump --host 대상서버아이피 --port 포트번호 --db db이름 --collection 컬렉션이름 --out 백업시킬경로 --query 쿼리조건

 

 

예)

저희가 저장하고 있는 incident_traffic 컬렉션의 데이터 중

time : 2014-02-18 00:12:41

이렇게 시간 컬럼이 있습니다.

 

백업 서버 : 1.1.1.1

데이터가 실제 있는 서버 : 2.2.2.2

몽고DB의 포트 : 27017

db 이름 : aptsys

 

하루동안의 데이터만 백업시키려면 다음과 같이 합니다

 

mongodump -u userID -p userPW --host 2.2.2.2 --port 27017 --db aptsys --collection incident_traffic --out /home/mongo/backup --query '{time:{"$gte":"2014-02-18 00:00:00" ,"$lte":"2014-02-18 23:59:59"}}'

 

time 필드가 아니고 info 필드 내의 time 필드 (?) 일 경우 이와 같이 사용

> info.time

 

1.1.1.1 서버의 /home/mongo/backup 의 공간에 해당 데이터들이 쌓입니다.

 

주의점 -> 항상 전체 컬렉션에서 해당 쿼리에 맞는 데이터를 쌓기 때문에 오늘 덤프를 뜨든, 내일 덤프를 뜨든 늘 전체 데이터를 full scan 합니다. 항상 전체 데이터에서 퍼센테이지 올라가는거보고 " 왜 쿼리가 안먹혀? " 라고 생각하지 마세요~

 

백업된 dump 파일을 서버로 옮기려면

mongorestore 를 합니다.

 

mongorestore --host 127.0.0.1 --port 27017 /home/mongo/backup/aptsys

 

 

[출처] https://m.blog.naver.com/PostView.nhn?blogId=atoz333&logNo=120207822356&proxyReferer=https%3A%2F%2Fwww.google.com%2F

반응형

댓글()

mysqldump 사용시 조건을 주어 백업하기

리눅스/MySQL|2019. 6. 18. 08:16
반응형

# mysqldump -u root -p {DB명} {table명} --where="id='1234'" > filename.sql

 

위와 같이 --where 옵션에 조건을 주면 된다.

 

 

 

반응형

댓글()

[디스크 포맷에러] /dev/sdb1 is apparently in use by the system; will not make a filesystem here

리눅스/OS 일반|2019. 6. 17. 15:28
반응형

[리눅스에서 아래와 같은  디스크 포맷에러가 날 경우]

 

 /dev/sdb1 is apparently in use by the system; will not make a filesystem here'

 

dmsetup 명령을 통해

시스템에서 사용중인 Disk Device 에 대한 상태정보를 확인후

시스템에서 실제 사용중이지 않은 Garbage 정보를 Clear 한 후 파일 시스템 작성이 가능하다.

 

# dmsetup status
ddf1_1TB2ea0: 0 2097152 linear 
ddf1_1TB2ea0: 0 18612224 linear

 

# dmsetup remove_all    // 현재 사용중인 디스크는 안지워지므로 안심해도 됨

 

# dmsetup status
No device found!

 

#mkfs.ext4 /dev/sdb 
정상적인 포맷진행 ~~

 

 

[출처] http://dogsale.co.kr/bbs/board.php?bo_table=useguide&wr_id=308

반응형

'리눅스 > OS 일반' 카테고리의 다른 글

Google OTP를 사용한 SSH 인증  (0) 2019.06.21
SAMBA 서비스 포트 변경 및 마운트 하기  (0) 2019.06.19
haproxy on CentOS 7  (0) 2019.05.29
STUN 에 대한 설명  (0) 2019.05.16
ipvsadm (로드밸런싱) 설정  (0) 2019.05.16

댓글()

Ubuntu 에서 Apache 2.4 와 Tomcat 8 (2대) 연동하기

리눅스/APACHE|2019. 6. 12. 13:09
반응형

Apache 서버 : 49.247.214.95

Tomcat 서버 : 49.247.213.105

 

1. Apache 설치 및 설정

(Apache 서버에서)

[root@sysdocu ~]# apt-get -y update

[root@sysdocu ~]# apt-get -y install apache2

[root@sysdocu ~]# apt-get -y install libapache2-mod-jk

[root@sysdocu ~]# vi /etc/apache2/workers.properties

workers.tomcat_home=/usr/share/tomcat8    // Apache 서버에 이런 주소가 없어도 상관 없음
workers.java_home=/usr/lib/jvm/java-8-openjdk-amd64
# Define 1 real worker ajp13
worker.list=tomcat1
# Set properties for tomcat1 (ajp13)
worker.tomcat1.port = 8009
worker.tomcat1.host = 192.168.10.2   // Apache 와 Tomcat 을 하나의 서버로 운영한다면 'localhost' 로 변경
worker.tomcat1.type = ajp13
worker.tomcat1.lbfactor = 20

 

Tomcat 서버가 두 대라면, 아래와 같이 셋팅합니다.

worker.list= tomcat1, tomcat2    // 이름은 임의로 설정
 
worker.tomcat1.type=ajp13
worker.tomcat1.host=톰캣서버 IP
worker.tomcat1.port=8009
worker.tomcat1.lbfactor=1
 
worker.tomcat2.type=ajp13
worker.tomcat2.host=톰캣서버 IP
worker.tomcat2.port=8009
worker.tomcat2.lbfactor=2

 

[root@sysdocu ~]# vi /etc/apache2/mods-available/jk.conf

...(생략)...
JkWorkersFile /etc/apache2/workers.properties
...(생략)...

 

[root@sysdocu ~]# vi /etc/apache2/sites-enabled/000-default.conf

...(생략)...
DocumentRoot /var/www/html
JkMount /*.jsp tomcat1    // jsp 확장자는 tomcat1 서버에서 구동 되도록 설정을 추가 합니다.

...(생략)...

 

[root@sysdocu ~]# /etc/init.d/apache2 restart

 

2. Tomcat 설치 및 설정

(Tomcat 서버에서)

[root@sysdocu ~]# apt-get -y update

[root@sysdocu ~]# apt-get -y install tomcat8

[root@sysdocu ~]# vi /etc/tomcat8/server.xml

<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />    // 주석 해제

 

[root@sysdocu ~]# /etc/init.d/tomcat8 restart

 

* Apache 서버에서 접속할 수 있게 iptables 방화벽에서 8009, 8080 포트를 오픈합니다.

 

3. 테스트

(Tomcat 서버에서)

[root@sysdocu ~]# vi /var/lib/tomcat8/webapps/ROOT/test.jsp

<%
String str = request.getParameter("name");
if(str == null)
{ str = "JSP"; }
%>
Hello, <%= str %>!!!

 

(PC 웹브라우저에서)

웹브라우저에서 아래와 같이 접속 테스트를 합니다.

 

http://192.168.10.2                      // 기본 아파치 index.html 페이지가 뜹니다.

http://192.168.10.2/test.jsp    // 톰캣 서버에서 생성한 test.jsp 페이지가 뜹니다.

 

반응형

댓글()

naverMap 을 전역 변수로 활용하기

반응형

네이버 맵을 사용하려고 보면

아래와 같이 public void onCreate(Bundle savedInstanceState) 영역 바깥쪽에 아래 코드를 넣어야 하고,

onMapReady 안에서만 naverMap 을 사용할 수 있었다.

이것은 액티비티가 열릴때 자동 실행되는 부분으로 모든 효과나 설정을 onMapReady 안에서만 처리가 가능했다.

사용자가 값을 변경해서 요청할 경우에는 사용하지 못하게... ㅜ.ㅜ

 

    @UiThread

    @Override

    public void onMapReady(@NonNull NaverMap naverMap) {

          naverMap.setMapType(NaverMap.MapType.Basic); // 지도 타입

          naverMap.setLayerGroupEnabled(NaverMap.LAYER_GROUP_TRANSIT, true); // 오버레이되는 정보 (대중교통 - 버스, 지하철)

        // 지정된 좌표로 카메라 이동

        CameraUpdate cameraUpdate = CameraUpdate.scrollTo(new LatLng(37.4685195, 126.8844555))

                                    .animate(CameraAnimation.Easing, 2000);

          naverMap.moveCamera(cameraUpdate);

    }

 

그래나 해결책을 찾았다. naverMap 을 전역 변수화 하면 다른곳에서도 사용이 가능하다.^^

 

public class MapActivity extends FragmentActivity implements OnMapReadyCallback {

    // 네이버맵 전역 변수
    private NaverMap NMap;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

 

          ... (생략) ...

          CameraUpdate cameraUpdate = CameraUpdate.scrollTo(new LatLng(37.5670135, 126.9783740))

                                                                              .animate(CameraAnimation.Easing, 2000);
          NMap.moveCamera(cameraUpdate); // 설정한 좌표로 카메라 이동 // onMapReady 밖에서도 사용 가능 ^^

          ... (생략) ...

    }

 

 

    // 네이버맵 초기 설정
    @UiThread
    @Override
    public void onMapReady(@NonNull NaverMap naverMap) {

        NMap = naverMap;

          NMap.setMapType(NaverMap.MapType.Basic); // 지도 타입
          NMap.setLayerGroupEnabled(NaverMap.LAYER_GROUP_TRANSIT, true); // 오버레이되는 정보 (대중교통 - 버스, 지하철)

    }

 

[참고] https://nittaku.tistory.com/69

            https://developers.google.com/maps/documentation/android-sdk/infowindows?hl=ko

반응형

댓글()

안드로이드 개발 Android GPS 정보 알아오기

반응형

안드로이드에서 GPS 정보를 가져온 후 자기 위치를 MAP 에 표시하거나 다른 사람에게 위치 정보를 알릴 수도 있습니다. 앱에서 GPS 정보는 아주 다양하게 쓰이기 때문에 별도의 클래스를 만들어서 사용하는 것이 좋겠죠. 스마트폰에 GPS 설정이 되어 있지 않을 때 팝업창을 띄워서 설정창으로 이동할 수 있는 소스도 추가가 되어 있습니다. 그리고 최근에 수정한 내용 중 API 23 버전부터 퍼미션을 추가해야 되는 부분도 참고 하시기 바랍니다.   

 

 

 먼저 AndroidManifest.xml  GPS 정보를 가져올 수 있도록 환경을 셋팅해야 합니다. 

<uses-permission android:name="android.permission.INTERNET" />

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

 

 GPS 정보를 알아오는 클래스에 대해서 알아봅니다. 클래스명은 GpsInfo.java 입니다. 추상클래스 Service 와 위치정보를 받아오기 위한 Listener 인터페이스 클래스 LocationListener 상속합니다. 그리고 GPS나 네트워크 사용유무, 얼마에 한번씩 데이터를 업데이트 할 것인지에 대한 변수들을 만듭니다. 실질적으로 위치정보를 알아오는 클래스는 LocationManager 입니다. 이것도 변수지정해 놓습니다. 

private final Context mContext;

  

// 현재 GPS 사용유무

boolean isGPSEnabled = false;

 

// 네트워크 사용유무

boolean isNetworkEnabled = false;

  

// GPS 상태값

boolean isGetLocation = false;

  

Location location;

double lat; // 위도

double lon; // 경도

  

// 최소 GPS 정보 업데이트 거리 10미터

private static final long MIN_DISTANCE_CHANGE_FOR_UPDATES = 10;

  

// 최소 GPS 정보 업데이트 시간 밀리세컨이므로 1분

private static final long MIN_TIME_BW_UPDATES = 1000 * 60 * 1;

  

protected LocationManager locationManager;

 

 다음은 GPS 위치값을 가져오기 위한 함수입니다. LocationManager 을 사용하였으며 requestLocationUpdates() 함수로 현재 정보를 업데이트 하고 getLastKnownLocation()

함수로 위치값을 가져옵니다. 

this.isGetLocation = true;

// 네트워크 정보로 부터 위치값 가져오기

if (isNetworkEnabled) {

    locationManager.requestLocationUpdates(

            LocationManager.NETWORK_PROVIDER,

            MIN_TIME_BW_UPDATES,

            MIN_DISTANCE_CHANGE_FOR_UPDATES, this);

    

    if (locationManager != null) {

        location = locationManager

                .getLastKnownLocation(LocationManager.NETWORK_PROVIDER);

        if (location != null) {

            // 위도 경도 저장

            lat = location.getLatitude();

            lon = location.getLongitude();

        }

    }

}

 

if (isGPSEnabled) {

    if (location == null) {

        locationManager.requestLocationUpdates(

                LocationManager.GPS_PROVIDER,

                MIN_TIME_BW_UPDATES,

                MIN_DISTANCE_CHANGE_FOR_UPDATES, this);

        if (locationManager != null) {

            location = locationManager

                    .getLastKnownLocation(LocationManager.GPS_PROVIDER);

            if (location != null) {

                lat = location.getLatitude();

                lon = location.getLongitude();

            }

        }

    }

}

 

 위 함수를 실행전에 GPS 상태정보와 네트워크 정보를 가져와 제대로 환경이 되어있지 않다면 정보를 가져오는 부분을 스킵하겠죠. 

// GPS 정보 가져오기

isGPSEnabled = locationManager.isProviderEnabled(

                           LocationManager.GPS_PROVIDER);

  

// 현재 네트워크 상태 값 알아오기

isNetworkEnabled = locationManager.isProviderEnabled(

                               LocationManager.NETWORK_PROVIDER);

 

 이렇게 환경정보를 확인했는데 제대로 되어있지 않다면 GPS 정보가 제대로 설정 되어있는지 확인을 위한 alert 창을 띄우게 되고, 설정창으로 바로 가서 다시 GPS 를 사용할수 있도록 셋팅페이지로 가게 하는 기능입니다.

 

 

public void showSettingsAlert(){

    AlertDialog.Builder alertDialog = new AlertDialog.Builder(mContext);

 

    alertDialog.setTitle("GPS 사용유무셋팅");

    alertDialog.setMessage("GPS 셋팅이 되지 않았을수도 있습니다.

            \n 설정창으로 가시겠습니까?");             

        // OK 를 누르게 되면 설정창으로 이동합니다.

    alertDialog.setPositiveButton("Settings", new DialogInterface.OnClickListener() {

        public void onClick(DialogInterface dialog,int which) {

            Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);

            mContext.startActivity(intent);

        }

    });

    // Cancle 하면 종료 합니다.

    alertDialog.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {

        public void onClick(DialogInterface dialog, int which) {

        dialog.cancel();

        }

    });

 

    alertDialog.show();

}

 

 안드로이드가 업그레이드 되면서 보안을 좀더 강화했는데 AndroidManifest.xml 에만 퍼미션을 넣어서는 안됩니다. 소스에서 사용자에게 퍼미션을 요청하는 로직을 넣어야 합니다. 그렇지 않으면 다음과 같은 에러가 발생합니다. 

 

 일단 GpsInfo.class 에 퍼미션 체크 로직을 넣어야 에러가 사라집니다. 

if ( Build.VERSION.SDK_INT >= 23 &&

        ContextCompat.checkSelfPermission(

                mContext, android.Manifest.permission.ACCESS_FINE_LOCATION )

                != PackageManager.PERMISSION_GRANTED &&

        ContextCompat.checkSelfPermission(

                mContext, android.Manifest.permission.ACCESS_COARSE_LOCATION)

                != PackageManager.PERMISSION_GRANTED) {

 

    return null;

}

 

 아래는 GPS 정보를 가져오기 위한 GpsInfo.java  전체 소스입니다. 

import android.annotation.TargetApi;

import android.app.AlertDialog;

import android.app.Service;

import android.content.Context;

import android.content.DialogInterface;

import android.content.Intent;

import android.content.pm.PackageManager;

import android.location.Location;

import android.location.LocationListener;

import android.location.LocationManager;

import android.os.Build;

import android.os.Bundle;

import android.os.IBinder;

import android.provider.Settings;

import android.support.v4.content.ContextCompat;

 

public class GpsInfo extends Service implements LocationListener {

 

    private final Context mContext;

 

    // 현재 GPS 사용유무

    boolean isGPSEnabled = false;

 

    // 네트워크 사용유무

    boolean isNetworkEnabled = false;

 

    // GPS 상태값

    boolean isGetLocation = false;

 

    Location location;

    double lat; // 위도

    double lon; // 경도

 

    // 최소 GPS 정보 업데이트 거리 10미터

    private static final long MIN_DISTANCE_CHANGE_FOR_UPDATES = 10;

 

    // 최소 GPS 정보 업데이트 시간 밀리세컨이므로 1분

    private static final long MIN_TIME_BW_UPDATES = 1000 * 60 * 1;

 

    protected LocationManager locationManager;

 

    public GpsInfo(Context context) {

        this.mContext = context;

        getLocation();

    }

 

    @TargetApi(23)

    public Location getLocation() {

        if ( Build.VERSION.SDK_INT >= 23 &&

                ContextCompat.checkSelfPermission(

                        mContext, android.Manifest.permission.ACCESS_FINE_LOCATION )

                        != PackageManager.PERMISSION_GRANTED &&

                ContextCompat.checkSelfPermission(

                        mContext, android.Manifest.permission.ACCESS_COARSE_LOCATION)

                        != PackageManager.PERMISSION_GRANTED) {

 

            return null;

        }

 

        try {

            locationManager = (LocationManager) mContext

                    .getSystemService(LOCATION_SERVICE);

 

            // GPS 정보 가져오기

            isGPSEnabled = locationManager

                    .isProviderEnabled(LocationManager.GPS_PROVIDER);

 

            // 현재 네트워크 상태 값 알아오기

            isNetworkEnabled = locationManager

                    .isProviderEnabled(LocationManager.NETWORK_PROVIDER);

 

            if (!isGPSEnabled && !isNetworkEnabled) {

                // GPS 와 네트워크사용이 가능하지 않을때 소스 구현

            } else {

                this.isGetLocation = true;

                // 네트워크 정보로 부터 위치값 가져오기

                if (isNetworkEnabled) {

                    locationManager.requestLocationUpdates(

                            LocationManager.NETWORK_PROVIDER,

                            MIN_TIME_BW_UPDATES,

                            MIN_DISTANCE_CHANGE_FOR_UPDATES, this);

 

                    if (locationManager != null) {

                        location = locationManager

                                .getLastKnownLocation(LocationManager.NETWORK_PROVIDER);

                        if (location != null) {

                            // 위도 경도 저장

                            lat = location.getLatitude();

                            lon = location.getLongitude();

                        }

                    }

                }

 

                if (isGPSEnabled) {

                    if (location == null) {

                        locationManager.requestLocationUpdates(

                                LocationManager.GPS_PROVIDER,

                                MIN_TIME_BW_UPDATES,

                                MIN_DISTANCE_CHANGE_FOR_UPDATES, this);

                        if (locationManager != null) {

                            location = locationManager

                                    .getLastKnownLocation(LocationManager.GPS_PROVIDER);

                            if (location != null) {

                                lat = location.getLatitude();

                                lon = location.getLongitude();

                            }

                        }

                    }

                }

            }

 

        } catch (Exception e) {

            e.printStackTrace();

        }

        return location;

    }

 

    /**

     * GPS 종료

     * */

    public void stopUsingGPS(){

        if(locationManager != null){

            locationManager.removeUpdates(GpsInfo.this);

        }

    }

 

    /**

     * 위도값을 가져옵니다.

     * */

    public double getLatitude(){

        if(location != null){

            lat = location.getLatitude();

        }

        return lat;

    }

 

    /**

     * 경도값을 가져옵니다.

     * */

    public double getLongitude(){

        if(location != null){

            lon = location.getLongitude();

        }

        return lon;

    }

 

    /**

     * GPS 나 wife 정보가 켜져있는지 확인합니다.

     * */

    public boolean isGetLocation() {

        return this.isGetLocation;

    }

 

    /**

     * GPS 정보를 가져오지 못했을때

     * 설정값으로 갈지 물어보는 alert 창

     * */

    public void showSettingsAlert(){

        AlertDialog.Builder alertDialog = new AlertDialog.Builder(mContext);

 

        alertDialog.setTitle("GPS 사용유무셋팅");

        alertDialog.setMessage("GPS 셋팅이 되지 않았을수도 있습니다. \n 설정창으로 가시겠습니까?");

 

                // OK 를 누르게 되면 설정창으로 이동합니다.

                alertDialog.setPositiveButton("Settings",

                        new DialogInterface.OnClickListener() {

                            public void onClick(DialogInterface dialog,int which) {

                                Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);

                                mContext.startActivity(intent);

                            }

                        });

        // Cancle 하면 종료 합니다.

        alertDialog.setNegativeButton("Cancel",

                new DialogInterface.OnClickListener() {

                    public void onClick(DialogInterface dialog, int which) {

                        dialog.cancel();

                    }

                });

 

        alertDialog.show();

    }

 

    @Override

    public IBinder onBind(Intent arg0) {

        return null;

    }

 

    public void onLocationChanged(Location location) {

        // TODO Auto-generated method stub

 

    }

 

    public void onStatusChanged(String provider, int status, Bundle extras) {

        // TODO Auto-generated method stub

 

    }

 

    public void onProviderEnabled(String provider) {

        // TODO Auto-generated method stub

 

    }

 

    public void onProviderDisabled(String provider) {

        // TODO Auto-generated method stub

 

    }

}

 

두 번째는 GPS 정보를 가져오는 Activity 에서의 퍼미션 체크 로직을 삽입해야 합니다. 기존에 없던 로직으로 callPermission() 함수가 그것입니다. 함수로 퍼미션 요청을 사용자에게 하면 그림과 같은 요청 팝업창이 뜹니다. 팝업창에서 DENY/ALLOW 하나를 선택하면 Override 한 onRequestPermissionsResult() 로 결과 값을 전달합니다.  

 

 

 이번에 추가한 callPermission() 와 onRequestPermissionsResult() 소스는 다음과 같습니다. 

@Override

public void onRequestPermissionsResult(int requestCode, String[] permissions,

                                       int[] grantResults) {

    if (requestCode == PERMISSIONS_ACCESS_FINE_LOCATION

            && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

 

        isAccessFineLocation = true;

 

    } else if (requestCode == PERMISSIONS_ACCESS_COARSE_LOCATION

            && grantResults[0] == PackageManager.PERMISSION_GRANTED){

 

        isAccessCoarseLocation = true;

    }

 

    if (isAccessFineLocation && isAccessCoarseLocation) {

        isPermission = true;

    }

}

 

// 전화번호 권한 요청

private void callPermission() {

    // Check the SDK version and whether the permission is already granted or not.

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M

            && checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)

            != PackageManager.PERMISSION_GRANTED) {

 

        requestPermissions(

                new String[]{Manifest.permission.ACCESS_FINE_LOCATION},

                PERMISSIONS_ACCESS_FINE_LOCATION);

 

    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M

            && checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION)

            != PackageManager.PERMISSION_GRANTED){

 

        requestPermissions(

                new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},

                PERMISSIONS_ACCESS_COARSE_LOCATION);

    } else {

        isPermission = true;

    }

}

 

 GPS 정보를 사용하는 메인 Activity 전체 소스입니다. Toast 를 이용해 화면에 위치 정보를 띄우고 TextView 에 위도와 경도를 알려줍니다. 

import android.Manifest;

import android.app.Activity;

import android.content.pm.PackageManager;

import android.os.Build;

import android.os.Bundle;

import android.view.View;

import android.widget.Button;

import android.widget.TextView;

import android.widget.Toast;

 

/** GPS 샘플 */

public class GpsActivity extends Activity {

 

    private Button btnShowLocation;

    private TextView txtLat;

    private TextView txtLon;

    private final int PERMISSIONS_ACCESS_FINE_LOCATION = 1000;

    private final int PERMISSIONS_ACCESS_COARSE_LOCATION = 1001;

    private boolean isAccessFineLocation = false;

    private boolean isAccessCoarseLocation = false;

    private boolean isPermission = false;

 

    // GPSTracker class

    private GpsInfo gps;

 

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_gps);

 

        btnShowLocation = (Button) findViewById(R.id.btn_start);

        txtLat = (TextView) findViewById(R.id.tv_latitude);

        txtLon = (TextView) findViewById(R.id.tv_longitude);

 

        // GPS 정보를 보여주기 위한 이벤트 클래스 등록

        btnShowLocation.setOnClickListener(new View.OnClickListener() {

            public void onClick(View arg0) {

                // 권한 요청을 해야 함

                if (!isPermission) {

                    callPermission();

                    return;

                }

 

                gps = new GpsInfo(GpsActivity.this);

                // GPS 사용유무 가져오기

                if (gps.isGetLocation()) {

 

                    double latitude = gps.getLatitude();

                    double longitude = gps.getLongitude();

 

                    txtLat.setText(String.valueOf(latitude));

                    txtLon.setText(String.valueOf(longitude));

 

                    Toast.makeText(

                            getApplicationContext(),

                            "당신의 위치 - \n위도: " + latitude + "\n경도: " + longitude,

                            Toast.LENGTH_LONG).show();

                } else {

                    // GPS 를 사용할수 없으므로

                    gps.showSettingsAlert();

                }

            }

        });

 

        callPermission();  // 권한 요청을 해야 함

    }

 

    @Override

    public void onRequestPermissionsResult(int requestCode, String[] permissions,

                                           int[] grantResults) {

        if (requestCode == PERMISSIONS_ACCESS_FINE_LOCATION

                && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

 

            isAccessFineLocation = true;

 

        } else if (requestCode == PERMISSIONS_ACCESS_COARSE_LOCATION

                && grantResults[0] == PackageManager.PERMISSION_GRANTED){

 

            isAccessCoarseLocation = true;

        }

 

        if (isAccessFineLocation && isAccessCoarseLocation) {

            isPermission = true;

        }

    }

 

    // 전화번호 권한 요청

    private void callPermission() {

        // Check the SDK version and whether the permission is already granted or not.

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M

                && checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)

                != PackageManager.PERMISSION_GRANTED) {

 

            requestPermissions(

                    new String[]{Manifest.permission.ACCESS_FINE_LOCATION},

                    PERMISSIONS_ACCESS_FINE_LOCATION);

 

        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M

                && checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION)

                != PackageManager.PERMISSION_GRANTED){

 

            requestPermissions(

                    new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},

                    PERMISSIONS_ACCESS_COARSE_LOCATION);

        } else {

            isPermission = true;

        }

    }

}

  

 

API 23 버전부터 아래 에러가 난다면 두 가지 permission 을 추가해야 합니다. ACCESS_FINE_LOCATION 은 이미 있고 ACCESS_COARSE_LOCATION 만 추가하면 되겠죠. 

 

Call requires permission which may be rejected by user. Code should explicitly check to see if permission is available.

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>


출처: https://mainia.tistory.com/1153 [녹두장군 - 상상을 현실로]

 

 

 

반응형

댓글()